diff --git a/bin/build_directus.sh b/bin/build_directus.sh new file mode 100644 index 0000000000..f99a927bcd --- /dev/null +++ b/bin/build_directus.sh @@ -0,0 +1,10 @@ +rm -rf .directus-build +git clone https://github.com/directus/directus.git .directus-build + +pushd .directus-build + npm install + gulp build + gulp deploy +popd + +rm -rf .directus-build diff --git a/bin/build_subtree.sh b/bin/build_subtree.sh new file mode 100644 index 0000000000..04dd71f2b3 --- /dev/null +++ b/bin/build_subtree.sh @@ -0,0 +1,27 @@ +git subsplit init git@github.com:directus/directus.git + +# Collection +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Collection:git@github.com:directus/directus-collection.git + +# Config +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Config:git@github.com:directus/directus-config.git + +# Permissions +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Permissions:git@github.com:directus/directus-permissions.git + +# Database +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Database:git@github.com:directus/directus-database.git + +# Filesystem +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Filesystem:git@github.com:directus/directus-filesystem.git + +# Hash +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Hash:git@github.com:directus/directus-hash.git + +# Hooks +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Hook:git@github.com:directus/directus-hook.git + +# Utils +git subsplit publish --heads="version/6.4" --no-tags --debug api/core/Directus/Util:git@github.com:directus/directus-php-utils.git + +rm -rf .subsplit diff --git a/bin/directus b/bin/directus new file mode 100755 index 0000000000..0106175b11 --- /dev/null +++ b/bin/directus @@ -0,0 +1,11 @@ +#!/usr/bin/env php +run(); + +?> diff --git a/bin/runtests.sh b/bin/runtests.sh new file mode 100755 index 0000000000..75a898fed5 --- /dev/null +++ b/bin/runtests.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# +# Command line runner for unit tests for composer projects +# (c) Del 2015 http://www.babel.com.au/ +# No Rights Reserved +# + +# +# Clean up after any previous test runs +# +mkdir -p documents +rm -rf documents/coverage-html-new +rm -f documents/coverage.xml + +# +# Run phpunit +# +vendor/bin/phpunit --coverage-html documents/coverage-html-new --coverage-clover documents/coverage.xml + +if [ -d documents/coverage-html-new ]; then + rm -rf documents/coverage-html + mv documents/coverage-html-new documents/coverage-html +fi + diff --git a/config/api_sample.php b/config/api_sample.php new file mode 100644 index 0000000000..3a12c8f2a4 --- /dev/null +++ b/config/api_sample.php @@ -0,0 +1,155 @@ + [ + 'path' => '/', + 'env' => 'development', + 'debug' => true, + 'default_language' => 'en', + 'timezone' => 'America/New_York', + ], + + 'settings' => [ + 'debug' => true, + 'displayErrorDetails' => true, + 'logger' => [ + 'name' => 'directus-api', + 'level' => Monolog\Logger::DEBUG, + 'path' => __DIR__ . '/logs/app.log', + ], + ], + + 'database' => [ + 'type' => 'mysql', + 'host' => 'localhost', + 'port' => 3306, + 'name' => 'directus', + 'username' => 'root', + 'password' => 'pass', + 'prefix' => '', // not used + 'engine' => 'InnoDB', + 'charset' => 'utf8mb4' + ], + + 'cache' => [ + 'enabled' => false, + 'response_ttl' => 3600, // seconds + 'adapter' => 'filesystem', + 'path' => '/storage/cache', + // 'pool' => [ + // 'adapter' => 'apc' + // ], + // 'pool' => [ + // 'adapter' => 'apcu' + // ], + // 'pool' => [ + // 'adapter' => 'filesystem', + // 'path' => '../cache/', // relative to the api directory + // ], + // 'pool' => [ + // 'adapter' => 'memcached', + // 'host' => 'localhost', + // 'port' => 11211 + // ], + // 'pool' => [ + // 'adapter' => 'redis', + // 'host' => 'localhost', + // 'port' => 6379 + // ], + ], + + 'filesystem' => [ + 'adapter' => 'local', + // By default media directory are located at the same level of directus root + // To make them a level up outsite the root directory + // use this instead + // Ex: 'root' => realpath(ROOT_PATH.'/../storage/uploads'), + // Note: ROOT_PATH constant doesn't end with trailing slash + 'root' => 'storage/uploads', + // This is the url where all the media will be pointing to + // here all assets will be (yourdomain)/storage/uploads + // same with thumbnails (yourdomain)/storage/uploads/thumbs + 'root_url' => '/storage/uploads', + 'root_thumb_url' => '/storage/uploads/thumbs', + // 'key' => 's3-key', + // 'secret' => 's3-key', + // 'region' => 's3-region', + // 'version' => 's3-version', + // 'bucket' => 's3-bucket' + ], + + // HTTP Settings + 'http' => [ + 'emulate_enabled' => false, + // can be null, or an array list of method to be emulated + // Ex: ['PATH', 'DELETE', 'PUT'] + // 'emulate_methods' => null, + 'force_https' => false + ], + + 'mail' => [ + 'transport' => 'mail', + 'from' => 'admin@admin.com' + ], + + 'cors' => [ + 'enabled' => false, + 'origin' => ['*'], + 'headers' => [ + ['Access-Control-Allow-Headers', 'Authorization, Content-Type, Access-Control-Allow-Origin'], + ['Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE'], + ['Access-Control-Allow-Credentials', 'false'] + ] + ], + + 'rate_limit' => [ + 'enabled' => false, + 'limit' => 100, // number of request + 'interval' => 60, // seconds + 'adapter' => 'redis', + 'host' => '127.0.0.1', + 'port' => 6379, + 'timeout' => 10 + ], + + 'hooks' => [], + + 'filters' => [], + + 'feedback' => [ + 'token' => 'a-kind-of-unique-token', + 'login' => true + ], + + // These tables will not be loaded in the directus schema + 'tableBlacklist' => [], + + 'auth' => [ + 'secret_key' => '', + 'social_providers' => [ + // 'okta' => [ + // 'client_id' => '', + // 'client_secret' => '', + // 'base_url' => 'https://dev-000000.oktapreview.com/oauth2/default' + // ], + // 'github' => [ + // 'client_id' => '', + // 'client_secret' => '' + // ], + // 'facebook' => [ + // 'client_id' => '', + // 'client_secret' => '', + // 'graph_api_version' => 'v2.8', + // ], + // 'google' => [ + // 'client_id' => '', + // 'client_secret' => '', + // 'hosted_domain' => '*', + // ], + // 'twitter' => [ + // 'identifier' => '', + // 'secret' => '' + // ] + ] + ], +]; diff --git a/config/migrations.php b/config/migrations.php new file mode 100644 index 0000000000..5c747e8a76 --- /dev/null +++ b/config/migrations.php @@ -0,0 +1,15 @@ + [ + 'migrations' => '%%PHINX_CONFIG_DIR%%/../migrations/db/schemas', + 'seeds' => '%%PHINX_CONFIG_DIR%%/../migrations/db/seeds' + ], + + 'version_order' => 'creation', + + 'environments' => [ + 'default_migration_table' => 'directus_migrations', + 'default_database' => 'development' + ] +]; diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/migrations/db/schemas/20180220023138_create_activity_table.php b/migrations/db/schemas/20180220023138_create_activity_table.php new file mode 100644 index 0000000000..5f4cd03473 --- /dev/null +++ b/migrations/db/schemas/20180220023138_create_activity_table.php @@ -0,0 +1,87 @@ +table('directus_activity', ['signed' => false]); + + $table->addColumn('type', 'string', [ + 'limit' => 45, + 'null' => false + ]); + + $table->addColumn('action', 'string', [ + 'limit' => 45, + 'null' => false + ]); + + $table->addColumn('user', 'integer', [ + 'signed' => false, + 'null' => false, + 'default' => 0 + ]); + + $table->addColumn('datetime', 'datetime', [ + 'default' => null + ]); + + $table->addColumn('ip', 'string', [ + 'limit' => 50, + 'default' => null + ]); + + $table->addColumn('user_agent', 'string', [ + 'limit' => 255 + ]); + + $table->addColumn('collection', 'string', [ + 'limit' => 64, + 'null' => false + ]); + + $table->addColumn('item', 'string',[ + 'limit' => 255 + ]); + + $table->addColumn('datetime_edited', 'datetime', [ + 'null' => true, + 'default' => null + ]); + + $table->addColumn('comment', 'text', [ + 'null' => true + ]); + + $table->addColumn('deleted_comment', 'boolean', [ + 'signed' => false, + 'null' => true, + 'default' => false + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023144_create_activity_read_table.php b/migrations/db/schemas/20180220023144_create_activity_read_table.php new file mode 100644 index 0000000000..52924f9130 --- /dev/null +++ b/migrations/db/schemas/20180220023144_create_activity_read_table.php @@ -0,0 +1,60 @@ +table('directus_activity_read', ['signed' => false]); + + $table->addColumn('activity', 'integer', [ + 'null' => false, + 'signed' => false + ]); + + $table->addColumn('user', 'integer', [ + 'signed' => false, + 'null' => false, + 'default' => 0 + ]); + + // TODO: Add the time when this was read? + // $table->addColumn('datetime', 'datetime', [ + // 'default' => null + // ]); + + $table->addColumn('read', 'boolean', [ + 'signed' => false, + 'default' => false + ]); + + $table->addColumn('archived', 'boolean', [ + 'signed' => false, + 'default' => false + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023152_create_collections_presets_table.php b/migrations/db/schemas/20180220023152_create_collections_presets_table.php new file mode 100644 index 0000000000..3581086c89 --- /dev/null +++ b/migrations/db/schemas/20180220023152_create_collections_presets_table.php @@ -0,0 +1,81 @@ +table('directus_collection_presets', ['signed' => false]); + + $table->addColumn('title', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('user', 'integer', [ + 'signed' => false, + 'null' => true + ]); + $table->addColumn('role', 'integer', [ + 'signed' => false, + 'null' => true + ]); + $table->addColumn('collection', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('search_query', 'string', [ + 'limit' => 100, + 'null' => true, + 'default' => null + ]); + $table->addColumn('filters', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('view_type', 'string', [ + 'limit' => 100, + 'null' => false + ]); + $table->addColumn('view_query', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('view_options', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('translation', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addIndex(['user', 'collection', 'title'], [ + 'unique' => true, + 'name' => 'idx_user_collection_title' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023157_create_collections_table.php b/migrations/db/schemas/20180220023157_create_collections_table.php new file mode 100644 index 0000000000..6552f94a5c --- /dev/null +++ b/migrations/db/schemas/20180220023157_create_collections_table.php @@ -0,0 +1,71 @@ +table('directus_collections', [ + 'id' => false, + 'primary_key' => 'collection' + ]); + + $table->addColumn('collection', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('item_name_template', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('preview_url', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('hidden', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => false + ]); + $table->addColumn('single', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => false + ]); + $table->addColumn('translation', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('note', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023202_create_fields_table.php b/migrations/db/schemas/20180220023202_create_fields_table.php new file mode 100644 index 0000000000..80d2308d94 --- /dev/null +++ b/migrations/db/schemas/20180220023202_create_fields_table.php @@ -0,0 +1,114 @@ +table('directus_fields', ['signed' => false]); + + $table->addColumn('collection', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('field', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('type', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('interface', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('options', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('locked', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => false + ]); + $table->addColumn('translation', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('readonly', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => false + ]); + $table->addColumn('required', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => false + ]); + $table->addColumn('sort', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + $table->addColumn('view_width', 'integer', [ + 'signed' => false, + 'null' => false, + 'default' => 4 + ]); + $table->addColumn('note', 'string', [ + 'limit' => 1024, + 'null' => true, + 'default' => null + ]); + $table->addColumn('hidden_input', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => 0 + ]); + $table->addColumn('validation', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('hidden_list', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => 0 + ]); + $table->addColumn('group', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + + $table->addIndex(['collection', 'field'], [ + 'unique' => true, + 'name' => 'idx_collection_field' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023208_create_files_table.php b/migrations/db/schemas/20180220023208_create_files_table.php new file mode 100644 index 0000000000..f149fb3aa4 --- /dev/null +++ b/migrations/db/schemas/20180220023208_create_files_table.php @@ -0,0 +1,116 @@ +table('directus_files', ['signed' => false]); + + $table->addColumn('filename', 'string', [ + 'limit' => 255, + 'null' => false, + 'default' => null + ]); + $table->addColumn('title', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('description', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('location', 'string', [ + 'limit' => 200, + 'null' => true, + 'default' => null + ]); + $table->addColumn('tags', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('width', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + $table->addColumn('height', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + $table->addColumn('filesize', 'integer', [ + 'signed' => false, + 'default' => 0 + ]); + $table->addColumn('duration', 'integer', [ + 'signed' => true, + 'null' => true, + 'default' => null + ]); + $table->addColumn('metadata', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('type', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null // unknown type? + ]); + $table->addColumn('charset', 'string', [ + 'limit' => 50, + 'null' => true, + 'default' => null + ]); + $table->addColumn('embed', 'string', [ + 'limit' => 200, + 'null' => true, + 'default' => NULL + ]); + $table->addColumn('folder', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + $table->addColumn('upload_user', 'integer', [ + 'signed' => false, + 'null' => false + ]); + // TODO: Make directus set this value to whatever default is on the server (UTC) + // In MySQL 5.5 and below doesn't support CURRENT TIMESTAMP on datetime as default + $table->addColumn('upload_date', 'datetime', [ + 'null' => false + ]); + $table->addColumn('storage_adapter', 'string', [ + 'limit' => 50, + 'null' => false, + 'default' => 'local' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023213_create_folders_table.php b/migrations/db/schemas/20180220023213_create_folders_table.php new file mode 100644 index 0000000000..cb0596414d --- /dev/null +++ b/migrations/db/schemas/20180220023213_create_folders_table.php @@ -0,0 +1,50 @@ +table('directus_folders', ['signed' => false]); + + $table->addColumn('name', 'string', [ + 'limit' => 191, + 'null' => false, + 'encoding' => 'utf8mb4' + ]); + $table->addColumn('parent_folder', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + + $table->addIndex(['name', 'parent_folder'], [ + 'unique' => true, + 'name' => 'idx_name_parent_folder' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023217_create_roles_table.php b/migrations/db/schemas/20180220023217_create_roles_table.php new file mode 100644 index 0000000000..0d8c33599f --- /dev/null +++ b/migrations/db/schemas/20180220023217_create_roles_table.php @@ -0,0 +1,68 @@ +table('directus_roles', ['signed' => false]); + + $table->addColumn('external_id', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + + $table->addColumn('name', 'string', [ + 'limit' => 100, + 'null' => false + ]); + $table->addColumn('description', 'string', [ + 'limit' => 500, + 'null' => true, + 'default' => NULL + ]); + $table->addColumn('ip_whitelist', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('nav_blacklist', 'text', [ + 'null' => true, + 'default' => null + ]); + + $table->addIndex('name', [ + 'unique' => true, + 'name' => 'idx_group_name' + ]); + + $table->addIndex('external_id', [ + 'unique' => true, + 'name' => 'idx_users_external_id' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023226_create_permissions_table.php b/migrations/db/schemas/20180220023226_create_permissions_table.php new file mode 100644 index 0000000000..db48b2ced8 --- /dev/null +++ b/migrations/db/schemas/20180220023226_create_permissions_table.php @@ -0,0 +1,94 @@ +table('directus_permissions', ['signed' => false]); + + $table->addColumn('collection', 'string', [ + 'limit' => 64, + 'null' => false, + ]); + $table->addColumn('role', 'integer', [ + 'signed' => false, + 'null' => false + ]); + $table->addColumn('status', 'string', [ + 'length' => 64, + 'default' => null, + 'null' => true + ]); + $table->addColumn('create', 'string', [ + 'signed' => false, + 'null' => true, + 'default' => null, + 'length' => 16, + ]); + $table->addColumn('read', 'string', [ + 'signed' => false, + 'null' => true, + 'default' => null, + 'length' => 16, + ]); + $table->addColumn('update', 'string', [ + 'signed' => false, + 'null' => true, + 'default' => null, + 'length' => 16, + ]); + $table->addColumn('delete', 'string', [ + 'signed' => false, + 'null' => true, + 'default' => null, + 'length' => 16, + ]); + $table->addColumn('navigate', 'boolean', [ + 'signed' => false, + 'null' => false, + 'default' => false, + ]); + $table->addColumn('comment', 'string', [ + 'limit' => 8, + 'null' => true, + 'default' => null + ]); + $table->addColumn('read_field_blacklist', 'string', [ + 'limit' => 1000, + 'null' => true, + 'default' => null, + 'encoding' => 'utf8' + ]); + $table->addColumn('write_field_blacklist', 'string', [ + 'limit' => 1000, + 'null' => true, + 'default' => NULL, + 'encoding' => 'utf8', + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023232_create_relations_table.php b/migrations/db/schemas/20180220023232_create_relations_table.php new file mode 100644 index 0000000000..c825bb06f8 --- /dev/null +++ b/migrations/db/schemas/20180220023232_create_relations_table.php @@ -0,0 +1,67 @@ +table('directus_relations', ['signed' => false]); + + $table->addColumn('collection_a', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('field_a', 'string', [ + 'limit' => 45, + 'null' => false + ]); + $table->addColumn('junction_key_a', 'string', [ + 'limit' => 64, + 'null' => true + ]); + $table->addColumn('junction_collection', 'string', [ + 'limit' => 64, + 'null' => true + ]); + $table->addColumn('junction_mixed_collections', 'string', [ + 'limit' => 64, + 'null' => true + ]); + $table->addColumn('junction_key_b', 'string', [ + 'limit' => 64, + 'null' => true + ]); + $table->addColumn('collection_b', 'string', [ + 'limit' => 64, + 'null' => true + ]); + $table->addColumn('field_b', 'string', [ + 'limit' => 64, + 'null' => true + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023238_create_revisions_table.php b/migrations/db/schemas/20180220023238_create_revisions_table.php new file mode 100644 index 0000000000..f9531166ce --- /dev/null +++ b/migrations/db/schemas/20180220023238_create_revisions_table.php @@ -0,0 +1,66 @@ +table('directus_revisions', ['signed' => false]); + + $table->addColumn('activity', 'integer', [ + 'null' => false, + 'signed' => false + ]); + $table->addColumn('collection', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('item', 'string', [ + 'limit' => 255 + ]); + $table->addColumn('data', 'text', [ + 'limit' => 4294967295 + ]); + $table->addColumn('delta', 'text', [ + 'limit' => 4294967295, + 'null' => true + ]); + $table->addColumn('parent_item', 'string', [ + 'limit' => 255, + 'null' => true + ]); + $table->addColumn('parent_collection', 'string', [ + 'limit' => 64, + 'null' => true + ]); + $table->addColumn('parent_changed', 'boolean', [ + 'signed' => false, + 'default' => false, + 'null' => true + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023243_create_settings_table.php b/migrations/db/schemas/20180220023243_create_settings_table.php new file mode 100644 index 0000000000..2617239879 --- /dev/null +++ b/migrations/db/schemas/20180220023243_create_settings_table.php @@ -0,0 +1,52 @@ +table('directus_settings', ['signed' => false]); + + $table->addColumn('scope', 'string', [ + 'limit' => 64, + 'default' => null + ]); + $table->addColumn('key', 'string', [ + 'limit' => 64, + 'null' => false + ]); + $table->addColumn('value', 'string', [ + 'limit' => 255, + 'default' => null + ]); + + $table->addIndex(['scope', 'key'], [ + 'unique' => true, + 'name' => 'idx_scope_name' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180220023248_create_users_table.php b/migrations/db/schemas/20180220023248_create_users_table.php new file mode 100644 index 0000000000..0a9994c576 --- /dev/null +++ b/migrations/db/schemas/20180220023248_create_users_table.php @@ -0,0 +1,152 @@ +table('directus_users', ['signed' => false]); + + $table->addColumn('status', 'integer', [ + 'signed' => false, + 'limit' => 1, + 'default' => 2 // Inactive + ]); + $table->addColumn('first_name', 'string', [ + 'limit' => 50, + 'null' => true, + 'default' => null + ]); + $table->addColumn('last_name', 'string', [ + 'limit' => 50, + 'null' => true, + 'default' => null + ]); + $table->addColumn('email', 'string', [ + 'limit' => 128, + 'null' => false + ]); + $table->addColumn('email_notifications', 'integer', [ + 'limit' => 1, + 'default' => 1 + ]); + $table->addColumn('password', 'string', [ + 'limit' => 255, + 'encoding' => 'utf8', + 'null' => true, + 'default' => null + ]); + $table->addColumn('avatar', 'integer', [ + 'signed' => false, + 'limit' => 11, + 'null' => true, + 'default' => null + ]); + $table->addColumn('company', 'string', [ + 'limit' => 191, + 'null' => true, + 'default' => null + ]); + $table->addColumn('title', 'string', [ + 'limit' => 191, + 'null' => true, + 'default' => null + ]); + $table->addColumn('locale', 'string', [ + 'limit' => 8, + 'null' => true, + 'default' => 'en-US' + ]); + $table->addColumn('high_contrast_mode', 'boolean', [ + 'signed' => false, + 'null' => true, + 'default' => false + ]); + $table->addColumn('locale_options', 'text', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('timezone', 'string', [ + 'limit' => 32, + 'default' => 'America/New_York' + ]); + $table->addColumn('last_ip', 'string', [ + 'limit' => 50, + 'null' => true, + 'default' => null + ]); + $table->addColumn('last_login', 'datetime', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('last_access', 'datetime', [ + 'null' => true, + 'default' => null + ]); + $table->addColumn('last_page', 'string', [ + 'limit' => 45, + 'null' => true, + 'default' => null + ]); + $table->addColumn('token', 'string', [ + 'limit' => 255, + 'encoding' => 'utf8', + 'null' => true, + 'default' => null + ]); + $table->addColumn('invite_token', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + $table->addColumn('invite_accepted', 'boolean', [ + 'signed' => false, + 'null' => true, + 'default' => false + ]); + $table->addColumn('external_id', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null + ]); + + $table->addIndex('email', [ + 'unique' => true, + 'name' => 'idx_users_email' + ]); + + $table->addIndex('token', [ + 'unique' => true, + 'name' => 'idx_users_token' + ]); + + $table->addIndex('external_id', [ + 'unique' => true, + 'name' => 'idx_users_external_id' + ]); + + $table->create(); + } +} diff --git a/migrations/db/schemas/20180426173310_create_user_roles.php b/migrations/db/schemas/20180426173310_create_user_roles.php new file mode 100644 index 0000000000..299931c4d6 --- /dev/null +++ b/migrations/db/schemas/20180426173310_create_user_roles.php @@ -0,0 +1,52 @@ +table('directus_user_roles', ['signed' => false]); + + $table->addColumn('user', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + + $table->addColumn('role', 'integer', [ + 'signed' => false, + 'null' => true, + 'default' => null + ]); + + $table->addIndex(['user', 'role'], [ + 'unique' => true, + 'name' => 'idx_user_role' + ]); + + $table->create(); + } +} diff --git a/migrations/db/seeds/FieldsSeeder.php b/migrations/db/seeds/FieldsSeeder.php new file mode 100644 index 0000000000..b504c32d5f --- /dev/null +++ b/migrations/db/seeds/FieldsSeeder.php @@ -0,0 +1,837 @@ + 'directus_activity', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'type', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'action', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'user', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'user' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'datetime', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_DATETIME, + 'interface' => 'datetime' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'ip', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'user_agent', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'item', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_activity', + 'field' => 'comment', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_TEXT, + 'interface' => 'markdown' + ], + // Activity Read + [ + 'collection' => 'directus_activity_read', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_activity_read', + 'field' => 'activity', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_activity_read', + 'field' => 'user', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'user' + ], + [ + 'collection' => 'directus_activity_read', + 'field' => 'read', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_activity_read', + 'field' => 'archived', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + // Collections + [ + 'collection' => 'directus_collections', + 'field' => 'collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_collections', + 'field' => 'item_name_template', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_collections', + 'field' => 'preview_url', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_collections', + 'field' => 'hidden', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_collections', + 'field' => 'single', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_collections', + 'field' => 'translation', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'JSON' + ], + [ + 'collection' => 'directus_collections', + 'field' => 'note', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + // Collection Presets + [ + 'collection' => 'directus_collection_presets', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'title', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'user', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'user' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'role', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'search_query', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'filters', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'view_options', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'view_type', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'view_query', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_collection_presets', + 'field' => 'translation', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'JSON' + ], + // Fields + [ + 'collection' => 'directus_fields', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'field', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'type', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'interface', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'options', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'locked', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'translation', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'JSON' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'readonly', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'required', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'sort', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'sort' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'note', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'hidden_input', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'hidden_list', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'view_width', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'numeric' + ], + [ + 'collection' => 'directus_fields', + 'field' => 'group', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + // Files + [ + 'collection' => 'directus_files', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_files', + 'field' => 'filename', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'title', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'description', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_TEXT, + 'interface' => 'textarea' + ], + [ + 'collection' => 'directus_files', + 'field' => 'location', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'tags', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_CSV, + 'interface' => 'tags' + ], + [ + 'collection' => 'directus_files', + 'field' => 'width', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'numeric' + ], + [ + 'collection' => 'directus_files', + 'field' => 'height', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'numeric' + ], + [ + 'collection' => 'directus_files', + 'field' => 'filesize', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'filesize' + ], + [ + 'collection' => 'directus_files', + 'field' => 'duration', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'numeric' + ], + [ + 'collection' => 'directus_files', + 'field' => 'metadata', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'JSON' + ], + [ + 'collection' => 'directus_files', + 'field' => 'type', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'charset', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'embed', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'folder', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_files', + 'field' => 'upload_user', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'user' + ], + [ + 'collection' => 'directus_files', + 'field' => 'upload_date', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_DATETIME, + 'interface' => 'datetime' + ], + [ + 'collection' => 'directus_files', + 'field' => 'storage_adapter', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'data', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BLOB, + 'interface' => 'blob', + 'options' => '{ "nameField": "filename", "sizeField": "filesize", "typeField": "type" }' + ], + [ + 'collection' => 'directus_files', + 'field' => 'url', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_files', + 'field' => 'storage', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_ALIAS, + 'interface' => 'file-upload' + ], + // Folders + [ + 'collection' => 'directus_folders', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_folders', + 'field' => 'name', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_folders', + 'field' => 'parent_folder', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + // Roles + [ + 'collection' => 'directus_roles', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_roles', + 'field' => 'name', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_roles', + 'field' => 'description', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'textarea' + ], + [ + 'collection' => 'directus_roles', + 'field' => 'ip_whitelist', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_TEXT, + 'interface' => 'textarea' + ], + [ + 'collection' => 'directus_roles', + 'field' => 'nav_blacklist', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_TEXT, + 'interface' => 'textarea' + ], + // User Roles + [ + 'collection' => 'directus_user_roles', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_user_roles', + 'field' => 'user', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'user' + ], + [ + 'collection' => 'directus_user_roles', + 'field' => 'role', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + // Users + [ + 'collection' => 'directus_users', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_users', + 'field' => 'status', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'status', + 'options' => '{"status_mapping":[{"name": "draft"},{"name": "active"},{"name": "delete"}]}' + ], + [ + 'collection' => 'directus_users', + 'field' => 'first_name', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'last_name', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'email', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'roles', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_M2M, + 'interface' => 'm2m' + ], + [ + 'collection' => 'directus_users', + 'field' => 'email_notifications', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_users', + 'field' => 'password', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'password' + ], + [ + 'collection' => 'directus_users', + 'field' => 'avatar', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_FILE, + 'interface' => 'single-file' + ], + [ + 'collection' => 'directus_users', + 'field' => 'company', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'title', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'locale', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'locale_options', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_users', + 'field' => 'timezone', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'last_ip', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'last_login', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_DATETIME, + 'interface' => 'datetime' + ], + [ + 'collection' => 'directus_users', + 'field' => 'last_access', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_DATETIME, + 'interface' => 'datetime' + ], + [ + 'collection' => 'directus_users', + 'field' => 'last_page', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'token', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'invite_token', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_users', + 'field' => 'invite_accepted', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + // Permissions + [ + 'collection' => 'directus_permissions', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'role', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'status', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'create', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'read', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'update', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'delete', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'navigate', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'explain', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'allow_statuses', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_CSV, + 'interface' => 'tags' + ], + [ + 'collection' => 'directus_permissions', + 'field' => 'read_field_blacklist', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'textarea' + ], + // Relations + [ + 'collection' => 'directus_relations', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'collection_a', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'field_a', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'junction_key_a', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'junction_mixed_collections', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'junction_key_b', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'collection_b', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_relations', + 'field' => 'field_b', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + // Revisions + [ + 'collection' => 'directus_revisions', + 'field' => 'id', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'primary-key' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'activity', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'item', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'data', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_LONG_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'delta', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_LONG_JSON, + 'interface' => 'json' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'parent_item', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'parent_collection', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'many-to-one' + ], + [ + 'collection' => 'directus_revisions', + 'field' => 'parent_changed', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_BOOLEAN, + 'interface' => 'toggle' + ], + // Settings + [ + 'collection' => 'directus_settings', + 'field' => 'auto_sign_out', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_INT, + 'interface' => 'numeric' + ], + [ + 'collection' => 'directus_settings', + 'field' => 'youtube_api_key', + 'type' => \Directus\Database\Schema\DataTypes::TYPE_VARCHAR, + 'interface' => 'text-input' + ], + ]; + + $files = $this->table('directus_fields'); + $files->insert($data)->save(); + } +} diff --git a/migrations/db/seeds/FileSeeder.php b/migrations/db/seeds/FileSeeder.php new file mode 100644 index 0000000000..1d26519a7e --- /dev/null +++ b/migrations/db/seeds/FileSeeder.php @@ -0,0 +1,37 @@ + 1, + 'filename' => '00000000001.jpg', + 'title' => 'Mountain Range', + 'description' => 'A gorgeous view of this wooded mountain range', + 'location' => 'Earth', + 'tags' => 'trees,rocks,nature,mountains,forest', + 'width' => 1800, + 'height' => 1200, + 'filesize' => 602058, + 'type' => 'image/jpeg', + 'charset' => 'binary', + 'upload_user' => 1, + 'upload_date' => \Directus\Util\DateTimeUtils::nowInUTC()->toString(), + 'storage_adapter' => 'local' + ]; + + $files = $this->table('directus_files'); + $files->insert($data)->save(); + } +} diff --git a/migrations/db/seeds/RelationsSeeder.php b/migrations/db/seeds/RelationsSeeder.php new file mode 100644 index 0000000000..29756de4f0 --- /dev/null +++ b/migrations/db/seeds/RelationsSeeder.php @@ -0,0 +1,87 @@ + 'directus_activity', + 'field_a' => 'user', + 'collection_b' => 'directus_users' + ], + [ + 'collection_a' => 'directus_activity_read', + 'field_a' => 'user', + 'collection_b' => 'directus_users' + ], + [ + 'collection_a' => 'directus_activity_read', + 'field_a' => 'activity', + 'collection_b' => 'directus_activity' + ], + [ + 'collection_a' => 'directus_collections_presets', + 'field_a' => 'user', + 'collection_b' => 'directus_users' + ], + [ + 'collection_a' => 'directus_collections_presets', + 'field_a' => 'group', + 'collection_b' => 'directus_groups' + ], + [ + 'collection_a' => 'directus_files', + 'field_a' => 'upload_user', + 'collection_b' => 'directus_users' + ], + [ + 'collection_a' => 'directus_files', + 'field_a' => 'folder', + 'collection_b' => 'directus_folders' + ], + [ + 'collection_a' => 'directus_folders', + 'field_a' => 'parent_folder', + 'collection_b' => 'directus_folders' + ], + [ + 'collection_a' => 'directus_permissions', + 'field_a' => 'group', + 'collection_b' => 'directus_groups' + ], + [ + 'collection_a' => 'directus_revisions', + 'field_a' => 'activity', + 'collection_b' => 'directus_activity' + ], + [ + 'collection_a' => 'directus_users', + 'field_a' => 'roles', + 'junction_key_a' => 'user', + 'junction_collection' => 'directus_user_roles', + 'junction_key_b' => 'role', + 'field_b' => 'users', + 'collection_b' => 'directus_roles' + ], + [ + 'collection_a' => 'directus_users', + 'field_a' => 'avatar', + 'collection_b' => 'directus_files' + ] + ]; + + $files = $this->table('directus_relations'); + $files->insert($data)->save(); + } +} diff --git a/migrations/db/seeds/RolesSeeder.php b/migrations/db/seeds/RolesSeeder.php new file mode 100644 index 0000000000..ea2b7e1d90 --- /dev/null +++ b/migrations/db/seeds/RolesSeeder.php @@ -0,0 +1,33 @@ + 1, + 'name' => 'Administrator', + 'description' => 'Admins have access to all managed data within the system by default' + ], + [ + 'id' => 2, + 'name' => 'Public', + 'description' => 'This sets the data that is publicly available through the API without a token' + ] + ]; + + $groups = $this->table('directus_roles'); + $groups->insert($data)->save(); + } +} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000000..d124ba6e99 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,36 @@ +# Comment this line if you are getting: "Option SymLinksIfOwnerMatch not allowed here" error +# in Apache +Options +SymLinksIfOwnerMatch + + + RewriteEngine On + # Uncomment this if you are getting routing errors: + # RewriteBase /api + + RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Yield static media + RewriteCond %{REQUEST_FILENAME} !-f + + # Map extension requests to their front controller + # RewriteRule ^extensions/([^/]+) index.php?run_extension=$1&%{QUERY_STRING} [L] + + # Map all other requests to the main front controller, invoking the API router + RewriteRule ^ index.php?%{QUERY_STRING} [L] + + + + # Set CORS header for static files + Header set Access-Control-Allow-Origin "*" + + + + # Fix $HTTP_RAW_POST_DATA deprecated warning + php_value always_populate_raw_post_data -1 + + +# Prevent PageSpeed module from rewriting the templates files +# Avoiding it from breaking the template +# +# ModPagespeedDisallow "*/app/**/*.twig" +# diff --git a/public/extensions/core/auth/facebook/Provider.php b/public/extensions/core/auth/facebook/Provider.php new file mode 100644 index 0000000000..ef4a0e207d --- /dev/null +++ b/public/extensions/core/auth/facebook/Provider.php @@ -0,0 +1,41 @@ +provider = new Facebook([ + 'clientId' => $this->config->get('client_id'), + 'clientSecret' => $this->config->get('client_secret'), + 'redirectUri' => $this->getRedirectUrl(), + 'graphApiVersion' => $this->config->get('graph_api_version'), + ]); + + return $this->provider; + } +} diff --git a/public/extensions/core/auth/facebook/auth.php b/public/extensions/core/auth/facebook/auth.php new file mode 100644 index 0000000000..05ae212d88 --- /dev/null +++ b/public/extensions/core/auth/facebook/auth.php @@ -0,0 +1,5 @@ + \Directus\Authentication\Sso\Provider\facebook\Provider::class +]; diff --git a/public/extensions/core/auth/facebook/icon.svg b/public/extensions/core/auth/facebook/icon.svg new file mode 100644 index 0000000000..cad471a335 --- /dev/null +++ b/public/extensions/core/auth/facebook/icon.svg @@ -0,0 +1 @@ + diff --git a/public/extensions/core/auth/github/Provider.php b/public/extensions/core/auth/github/Provider.php new file mode 100644 index 0000000000..52e309046f --- /dev/null +++ b/public/extensions/core/auth/github/Provider.php @@ -0,0 +1,112 @@ +provider; + $ownerEmail = null; + $visible = []; + $primary = null; + + $url = $this->getResourceOwnerEmailUrl($token); + $request = $provider->getAuthenticatedRequest($provider::METHOD_GET, $url, $token); + $response = $provider->getParsedResponse($request); + + // Remove non-verified emails + $response = array_filter($response, function ($item) { + return ArrayUtils::get($item, 'verified') === true; + }); + + if (is_array($response) && count($response) > 0) { + // fallback to the first email on the list + $ownerEmail = $response[0]['email']; + + foreach ($response as $emailData) { + $email = ArrayUtils::get($emailData, 'email'); + + if (ArrayUtils::get($emailData, 'primary', false)) { + $primary = $email; + } + + if (ArrayUtils::get($emailData, 'visibility') === 'public') { + $visible[] = $email; + } + } + } + + // First try: pick primary email if it's visible + // Second try: pick the first visible email + // Third try: pick the primary email if exists + // Fourth try: pick the first email on the list + // Fifth try: fallback to null + if (in_array($primary, $visible)) { + $ownerEmail = $primary; + } else if (count($visible) > 0) { + $ownerEmail = array_shift($visible); + } else if ($primary) { + $ownerEmail = $primary; + } + + return $ownerEmail; + } + + /** + * Gets the resource owner email url + * + * @param AccessToken $token + * + * @return string + */ + protected function getResourceOwnerEmailUrl(AccessToken $token) + { + if ($this->provider->domain === 'https://github.com') { + $url = $this->provider->apiDomain . '/user/emails'; + } else { + $url = $this->provider->domain . '/api/v3/user/emails'; + } + + return $url; + } + + /** + * Creates the GitHub provider oAuth client + * + * @return Github + */ + protected function createProvider() + { + $this->provider = new Github([ + 'clientId' => $this->config->get('client_id'), + 'clientSecret' => $this->config->get('client_secret'), + 'redirectUri' => $this->getRedirectUrl(), + ]); + + return $this->provider; + } +} diff --git a/public/extensions/core/auth/github/auth.php b/public/extensions/core/auth/github/auth.php new file mode 100644 index 0000000000..4ec7a70817 --- /dev/null +++ b/public/extensions/core/auth/github/auth.php @@ -0,0 +1,5 @@ + \Directus\Authentication\Sso\Provider\github\Provider::class +]; diff --git a/public/extensions/core/auth/github/icon.svg b/public/extensions/core/auth/github/icon.svg new file mode 100644 index 0000000000..327dc84399 --- /dev/null +++ b/public/extensions/core/auth/github/icon.svg @@ -0,0 +1 @@ + diff --git a/public/extensions/core/auth/google/Provider.php b/public/extensions/core/auth/google/Provider.php new file mode 100644 index 0000000000..4049f4e161 --- /dev/null +++ b/public/extensions/core/auth/google/Provider.php @@ -0,0 +1,42 @@ +provider = new Google([ + 'clientId' => $this->config->get('client_id'), + 'clientSecret' => $this->config->get('client_secret'), + 'redirectUri' => $this->getRedirectUrl(), + 'hostedDomain' => $this->config->get('hosted_domain') + ]); + + return $this->provider; + } +} diff --git a/public/extensions/core/auth/google/auth.php b/public/extensions/core/auth/google/auth.php new file mode 100644 index 0000000000..7dd8e9873b --- /dev/null +++ b/public/extensions/core/auth/google/auth.php @@ -0,0 +1,5 @@ + \Directus\Authentication\Sso\Provider\google\Provider::class +]; diff --git a/public/extensions/core/auth/google/icon.svg b/public/extensions/core/auth/google/icon.svg new file mode 100644 index 0000000000..e47488aab8 --- /dev/null +++ b/public/extensions/core/auth/google/icon.svg @@ -0,0 +1 @@ + diff --git a/public/extensions/core/auth/okta/Provider.php b/public/extensions/core/auth/okta/Provider.php new file mode 100644 index 0000000000..6fbc94554e --- /dev/null +++ b/public/extensions/core/auth/okta/Provider.php @@ -0,0 +1,39 @@ +provider = new Okta([ + 'baseUrl' => $this->config->get('base_url'), + 'clientId' => $this->config->get('client_id'), + 'clientSecret' => $this->config->get('client_secret'), + 'redirectUri' => $this->getRedirectUrl() + ]); + + return $this->provider; + } +} diff --git a/public/extensions/core/auth/okta/auth.php b/public/extensions/core/auth/okta/auth.php new file mode 100644 index 0000000000..0acce0014d --- /dev/null +++ b/public/extensions/core/auth/okta/auth.php @@ -0,0 +1,5 @@ + \Directus\Authentication\Sso\Provider\okta\Provider::class +]; diff --git a/public/extensions/core/auth/okta/icon.svg b/public/extensions/core/auth/okta/icon.svg new file mode 100644 index 0000000000..e3e99799a0 --- /dev/null +++ b/public/extensions/core/auth/okta/icon.svg @@ -0,0 +1 @@ + diff --git a/public/extensions/core/auth/twitter/Provider.php b/public/extensions/core/auth/twitter/Provider.php new file mode 100644 index 0000000000..40f059b709 --- /dev/null +++ b/public/extensions/core/auth/twitter/Provider.php @@ -0,0 +1,38 @@ +provider = new Twitter([ + 'identifier' => $this->config->get('identifier'), + 'secret' => $this->config->get('secret'), + 'callback_uri' => $this->getRedirectUrl(), + ]); + + return $this->provider; + } + + /** + * @inheritdoc + */ + public function getScopes() + { + return null; + } +} diff --git a/public/extensions/core/auth/twitter/auth.php b/public/extensions/core/auth/twitter/auth.php new file mode 100644 index 0000000000..a159049264 --- /dev/null +++ b/public/extensions/core/auth/twitter/auth.php @@ -0,0 +1,5 @@ + \Directus\Authentication\Sso\Provider\twitter\Provider::class +]; diff --git a/public/extensions/core/auth/twitter/icon.svg b/public/extensions/core/auth/twitter/icon.svg new file mode 100644 index 0000000000..82b33cfc0a --- /dev/null +++ b/public/extensions/core/auth/twitter/icon.svg @@ -0,0 +1 @@ + diff --git a/public/extensions/core/interfaces/blob/Interface.js b/public/extensions/core/interfaces/blob/Interface.js new file mode 100644 index 0000000000..8fb39901ae --- /dev/null +++ b/public/extensions/core/interfaces/blob/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=58)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,l){var s=typeof(e=e||{}).default;"object"!==s&&"function"!==s||(e=e.default);var u,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),a?(u=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},c._ssrRegister=u):o&&(u=l?function(){o.call(this,this.$root.$options.shadowRoot)}:o),u)if(c.functional){c._injectStyles=u;var f=c.render;c.render=function(e,t){return u.call(t),f(e,t)}}else{var p=c.beforeCreate;c.beforeCreate=p?[].concat(p,u):[u]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},160:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,"/*\n * FilePondPluginImagePreview 1.0.8\n * Licensed under MIT, https://opensource.org/licenses/MIT\n * Please visit https://pqina.nl/filepond for details.\n */\n.filepond--image-preview-wrapper{z-index:2}.filepond--image-preview-overlay{display:block;position:absolute;left:0;top:0;width:100%;min-height:5rem;max-height:7rem;margin:0;opacity:0;z-index:1;mix-blend-mode:multiply;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--image-preview-overlay:nth-of-type(2),.filepond--image-preview-overlay:nth-of-type(3){mix-blend-mode:normal}@supports (-webkit-marquee-repetition:infinite) and (object-fit:fill){.filepond--image-preview-overlay{mix-blend-mode:normal}}.filepond--image-preview-wrapper{pointer-events:none;position:absolute;left:0;top:0;right:0;margin:0;border-radius:.45em;overflow:hidden;background:rgba(0,0,0,.01)}.filepond--image-preview{position:relative;z-index:1;display:block;width:100%;height:auto;pointer-events:none;-webkit-transform-origin:center center;transform-origin:center center;background:#222;will-change:transform,opacity}.filepond--image-preview div{position:relative;overflow:hidden;margin:0 auto}.filepond--image-preview canvas{position:absolute;left:0;top:0;will-change:transform}",""])},161:function(e,t,n){var r=n(160);"string"==typeof r&&(r=[[e.i,r,""]]);n(5)(r,{hmr:!0,transform:void 0,insertInto:void 0}),r.locals&&(e.exports=r.locals)},162:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,'/*\n * FilePond 1.2.10\n * Licensed under MIT, https://opensource.org/licenses/MIT\n * Please visit https://pqina.nl/filepond for details.\n */\n.filepond--assistant{position:absolute;overflow:hidden;height:1px;width:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap}.filepond--browser{position:absolute;margin:0;padding:0;left:1em;top:1.75em;width:calc(100% - 2em);opacity:0;font-size:0}.filepond--drip{position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden;opacity:.1;pointer-events:none;border-radius:.5em;background:rgba(0,0,0,.01)}.filepond--drip-blob{-webkit-transform-origin:center center;transform-origin:center center;left:0;width:8em;height:8em;margin-left:-4em;margin-top:-4em;background:#292625;border-radius:50%}.filepond--drip-blob,.filepond--drop-label{position:absolute;top:0;will-change:transform,opacity}.filepond--drop-label{left:1em;right:1em;margin:0 0 1em;color:#4f4f4f}.filepond--drop-label label{display:block;padding:1em 0;margin:0;cursor:default;font-size:.875em;font-weight:400;text-align:center;line-height:1.5}.filepond--label-action{text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip:ink;-webkit-text-decoration-color:#a7a4a4;text-decoration-color:#a7a4a4;cursor:pointer}.filepond--file-action-button{font-size:1em;width:1.625em;height:1.625em;cursor:auto;font-family:inherit;line-height:inherit;margin:0;padding:0;border:none;color:#fff;outline:none;border-radius:50%;background-color:rgba(0,0,0,.5);background-image:none;will-change:transform,opacity;box-shadow:0 0 0 0 hsla(0,0%,100%,0);transition:box-shadow .25s ease-in}.filepond--file-action-button svg{width:100%;height:100%}.filepond--file-action-button:focus,.filepond--file-action-button:hover{box-shadow:0 0 0 .125em hsla(0,0%,100%,.9)}.filepond--file-info{position:static;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex:1;flex:1;margin:0 .5em 0 0;min-width:0;will-change:transform,opacity;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--file-info *{margin:0}.filepond--file-info .filepond--file-info-main{font-size:.75em;line-height:1.2;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:100%}.filepond--file-info .filepond--file-info-sub{font-size:.625em;opacity:.5}.filepond--file-info .filepond--file-info-sub:empty{display:none}.filepond--file-status{position:static;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:end;align-items:flex-end;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;margin:0;min-width:2.25em;text-align:right;will-change:transform,opacity;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--file-status *{margin:0;white-space:nowrap}.filepond--file-status .filepond--file-status-main{font-size:.75em;line-height:1.2}.filepond--file-status .filepond--file-status-sub{font-size:.625em;opacity:.5}.filepond--file-wrapper{border:none;margin:0;padding:0;min-width:0;height:100%}.filepond--file-wrapper>legend{position:absolute;overflow:hidden;height:1px;width:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap}.filepond--file{position:static;display:-ms-flexbox;display:flex;height:100%;padding:.5625em;color:#fff;border-radius:.5em}.filepond--file .filepond--file-status{margin-left:auto;margin-right:2.25em}.filepond--file .filepond--file-action-button{position:absolute}.filepond--file .filepond--progress-indicator{position:absolute;top:.75em;right:.75em}.filepond--file .filepond--action-remove-item{left:.5625em}.filepond--file .filepond--file-action-button:not(.filepond--action-remove-item){right:.5625em}[data-filepond-item-state*=error] .filepond--file-info,[data-filepond-item-state*=invalid] .filepond--file-info,[data-filepond-item-state=cancelled] .filepond--file-info{margin-right:2.25em}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing svg{-webkit-animation:fall .5s .125s linear both;animation:fall .5s .125s linear both}[data-filepond-item-state*=error] .filepond--file-wrapper,[data-filepond-item-state*=error] .filepond--panel,[data-filepond-item-state*=invalid] .filepond--file-wrapper,[data-filepond-item-state*=invalid] .filepond--panel{-webkit-animation:shake .65s linear both;animation:shake .65s linear both}[data-filepond-item-state*=busy] .filepond--progress-indicator svg{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes shake{10%,90%{-webkit-transform:translateX(-.0625em);transform:translateX(-.0625em)}20%,80%{-webkit-transform:translateX(.125em);transform:translateX(.125em)}30%,50%,70%{-webkit-transform:translateX(-.25em);transform:translateX(-.25em)}40%,60%{-webkit-transform:translateX(.25em);transform:translateX(.25em)}}@keyframes shake{10%,90%{-webkit-transform:translateX(-.0625em);transform:translateX(-.0625em)}20%,80%{-webkit-transform:translateX(.125em);transform:translateX(.125em)}30%,50%,70%{-webkit-transform:translateX(-.25em);transform:translateX(-.25em)}40%,60%{-webkit-transform:translateX(.25em);transform:translateX(.25em)}}@-webkit-keyframes fall{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}70%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}to{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes fall{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}70%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}to{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.filepond--hopper[data-hopper-state=drag-over]>*{pointer-events:none}.filepond--progress-indicator{z-index:103}.filepond--file-action-button{z-index:102}.filepond--file-status{z-index:101}.filepond--file-info{z-index:100}.filepond--item{position:absolute;top:0;left:0;right:0;padding:0;margin:0 0 .5em;will-change:transform,opacity}.filepond--item>.filepond--panel{z-index:1}.filepond--item>.filepond--panel .filepond--panel-bottom{box-shadow:0 .0625em .125em -.0625em rgba(0,0,0,.25)}.filepond--item>.filepond--file-wrapper{position:relative;z-index:2}.filepond--item-panel{background-color:#64605e}[data-filepond-item-state=processing-complete] .filepond--item-panel{background-color:#369763}[data-filepond-item-state*=error] .filepond--item-panel,[data-filepond-item-state*=invalid] .filepond--item-panel{background-color:#c44e47}.filepond--item-panel{border-radius:.5em;transition:background-color .25s}.filepond--list-scroller{position:absolute;top:0;left:0;right:0;margin:0;will-change:transform}.filepond--list-scroller[data-state=overflow]{overflow-y:scroll;overflow-x:visible;-webkit-overflow-scrolling:touch}.filepond--list-scroller[data-state=overflow] .filepond--list{bottom:0;right:0}.filepond--list-scroller::-webkit-scrollbar{background:transparent}.filepond--list-scroller::-webkit-scrollbar:vertical{width:1em}.filepond--list-scroller::-webkit-scrollbar:horizontal{height:0}.filepond--list-scroller::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.3);border-radius:99999px;border:.3125em solid transparent;background-clip:content-box}.filepond--list{position:absolute;top:0;left:1em;right:1em;margin:0;padding:0;list-style-type:none;will-change:transform}.filepond--panel-root{border-radius:.5em;background-color:#f1f0ef}.filepond--panel{position:absolute;left:0;top:0;right:0;margin:0;height:auto!important;pointer-events:none}.filepond--panel[data-scalable=true]{-webkit-transform-style:preserve-3d;transform-style:preserve-3d;background-color:transparent!important;border:none!important}.filepond--panel[data-scalable=false]{bottom:0}.filepond--panel[data-scalable=false]>div{display:none}.filepond--panel-bottom,.filepond--panel-center,.filepond--panel-top{position:absolute;left:0;top:0;right:0;margin:0;padding:0}.filepond--panel-bottom,.filepond--panel-top{height:.5em}.filepond--panel-top{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important;border-bottom:none!important}.filepond--panel-top:after{content:"";position:absolute;height:2px;left:0;right:0;bottom:-1px;background-color:inherit}.filepond--panel-bottom,.filepond--panel-center{will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:left top;transform-origin:left top;-webkit-transform:translate3d(0,.5em,0);transform:translate3d(0,.5em,0)}.filepond--panel-bottom{border-top-left-radius:0!important;border-top-right-radius:0!important;border-top:none!important}.filepond--panel-bottom:before{content:"";position:absolute;height:2px;left:0;right:0;top:-1px;background-color:inherit}.filepond--panel-center{height:100px!important;border-top:none!important;border-bottom:none!important;border-radius:0!important}.filepond--panel-center:not([style]){visibility:hidden}.filepond--progress-indicator{position:static;width:1.25em;height:1.25em;color:#fff;margin:0;pointer-events:none;will-change:transform,opacity}.filepond--progress-indicator svg{width:100%;height:100%}.filepond--progress-indicator path{fill:none;stroke:currentColor}.filepond--list-scroller{z-index:6}.filepond--drop-label{z-index:5}.filepond--drip{z-index:3}.filepond--root>.filepond--panel{z-index:2}.filepond--browser{z-index:1}.filepond--root{box-sizing:border-box;position:relative;margin-bottom:1em;padding-top:1em;font-size:1rem;line-height:normal;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:450;text-align:left;text-rendering:optimizeLegibility;direction:ltr;contain:layout style size}.filepond--root *{font-size:inherit;box-sizing:inherit;line-height:inherit}',""])},163:function(e,t,n){var r=n(162);"string"==typeof r&&(r=[[e.i,r,""]]);n(5)(r,{hmr:!0,transform:void 0,insertInto:void 0}),r.locals&&(e.exports=r.locals)},21:function(e,t,n){var r,o,i,a,l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};a=function(e){"use strict";var t,n,r="function"==typeof Symbol&&"symbol"===l(Symbol.iterator)?function(e){return void 0===e?"undefined":l(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":void 0===e?"undefined":l(e)},o=(function(){function e(e){var t,n;function r(t,n){try{var i=e[t](n),a=i.value;a instanceof function(e){this.value=e}?Promise.resolve(a.value).then(function(e){r("next",e)},function(e){r("throw",e)}):o(i.done?"return":"normal",i.value)}catch(e){o("throw",e)}}function o(e,o){switch(e){case"return":t.resolve({value:o,done:!0});break;case"throw":t.reject(o);break;default:t.resolve({value:o,done:!1})}(t=t.next)?r(t.key,t.arg):n=null}this._invoke=function(e,o){return new Promise(function(i,a){var l={key:e,arg:o,resolve:i,reject:a,next:null};n?n=n.next=l:(t=n=l,r(e,o))})},"function"!=typeof e.return&&(this.return=void 0)}"function"==typeof Symbol&&Symbol.asyncIterator&&(e.prototype[Symbol.asyncIterator]=function(){return this}),e.prototype.next=function(e){return this._invoke("next",e)},e.prototype.throw=function(e){return this._invoke("throw",e)},e.prototype.return=function(e){return this._invoke("return",e)}}(),Object.assign||function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:null;if(null===n)return e.getAttribute(t)||e.hasAttribute(t);e.setAttribute(t,n)},c=["svg","path"],f=function(e){return c.includes(e)},p=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};"object"===(void 0===t?"undefined":r(t))&&(n=t,t=null);var o=f(e)?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e);return t&&(f(e)?u(o,"class",t):o.className=t),a(n,function(e,t){u(o,e,t)}),o},d=function(e,t,n,r){var i=n[0]||e.left,a=n[1]||e.top,l=i+e.width,s=a+e.height*(r[1]||1),u={element:o({},e),inner:{left:e.left,top:e.top,right:e.right,bottom:e.bottom},outer:{left:i,top:a,right:l,bottom:s}};return t.filter(function(e){return!e.isRectIgnored()}).map(function(e){return e.rect}).forEach(function(e){m(u.inner,o({},e.inner)),m(u.outer,o({},e.outer))}),h(u.inner),u.outer.bottom+=u.element.marginBottom,u.outer.right+=u.element.marginRight,h(u.outer),u},m=function(e,t){t.top+=e.top,t.right+=e.left,t.bottom+=e.top,t.left+=e.left,t.bottom>e.bottom&&(e.bottom=t.bottom),t.right>e.right&&(e.right=t.right)},h=function(e){e.width=e.right-e.left,e.height=e.bottom-e.top},v=function(e){return"number"==typeof e},g=function(e){return e<.5?2*e*e:(4-2*e)*e-1},E={spring:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.stiffness,n=void 0===t?.5:t,r=e.damping,o=void 0===r?.75:r,i=e.mass,a=void 0===i?10:i,l=null,u=null,c=0,f=!1,p=s({interpolate:function(){if(!f){if(!v(l)||!v(u))return f=!0,void(c=0);!function(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:.001;return Math.abs(e-t)0&&void 0!==arguments[0]?arguments[0]:{},t=e.duration,n=void 0===t?500:t,r=e.easing,o=void 0===r?g:r,i=e.delay,a=void 0===i?0:i,l=null,u=void 0,c=void 0,f=!0,p=!1,d=null,m=s({interpolate:function(e){f||null===d||(null===l&&(l=e),e-l=0?o(p?1-c:c):0)*d)):(u=1,f=!0,c=p?0:1,m.onupdate(c*d),m.oncomplete(c*d))))},target:{get:function(){return p?0:d},set:function(e){if(null===d)return d=e,m.onupdate(e),void m.oncomplete(e);e3&&void 0!==arguments[3]&&arguments[3];(t=Array.isArray(t)?t:[t]).forEach(function(t){e.forEach(function(e){var i=e,a=function(){return n[e]},l=function(t){return n[e]=t};"object"===(void 0===e?"undefined":r(e))&&(i=e.key,a=e.getter||a,l=e.setter||l),t[i]&&!o||(t[i]={get:a,set:l})})})},y=function(e){return null==e},T=function(e){return!y(e)},b={opacity:1,scaleX:1,scaleY:1,translateX:0,translateY:0,rotateX:0,rotateY:0,rotateZ:0},R={styles:function(e){var t=e.mixinConfig,n=e.viewProps,r=e.viewInternalAPI,a=e.viewExternalAPI,l=e.view,s=o({},n),u={};I(t,[r,a],n);var c=function(){return l.rect?d(l.rect,l.childViews,[n.translateX||0,n.translateY||0],[n.scaleX||0,n.scaleY||0]):null};return r.rect={get:c},a.rect={get:c},t.forEach(function(e){n[e]=void 0===s[e]?b[e]:s[e]}),{write:function(){if(function(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!0;for(var n in t)if(t[n]!==e[n])return!0;return!1}(u,n))return function(e,t){var n=t.opacity,r=t.translateX,o=t.translateY,i=t.scaleX,a=t.scaleY,l=t.rotateX,s=t.rotateY,u=t.rotateZ,c=t.height,f=[],p=[];(T(r)||T(o))&&f.push("translate3d("+(r||0)+"px, "+(o||0)+"px, 0)"),(T(i)||T(a))&&f.push("scale3d("+(T(i)?i:1)+", "+(T(a)?a:1)+", 1)"),(T(u)||T(s)||T(l))&&f.push("rotate3d("+(l||0)+", "+(s||0)+", "+(u||0)+", 360deg)"),f.length&&p.push("transform:"+f.join(" ")),T(n)&&(p.push("opacity:"+n),0===n&&p.push("visibility:hidden"),n<1&&p.push("pointer-events:none;")),T(c)&&p.push("height:"+c+"px");var d=e.getAttribute("style")||"",m=p.join(";");m.length===d.length&&m===d||e.setAttribute("style",m)}(l.element,n),Object.assign.apply(Object,[u].concat(i(n))),!0},destroy:function(){}}},listeners:function(e){e.mixinConfig,e.viewProps,e.viewInternalAPI;var t,n=e.viewExternalAPI,r=(e.viewState,e.view),o=[],i=(t=r.element,function(e,n){t.addEventListener(e,n)}),a=function(e){return function(t,n){e.removeEventListener(t,n)}}(r.element);return n.on=function(e,t){o.push({type:e,fn:t}),i(e,t)},n.off=function(e,t){o.splice(o.findIndex(function(n){return n.type===e&&n.fn===t}),1),a(e,t)},{write:function(){return!0},destroy:function(){o.forEach(function(e){a(e.type,e.fn)})}}},animations:function(e){var t=e.mixinConfig,n=e.viewProps,r=e.viewInternalAPI,i=e.viewExternalAPI,l=o({},n),s=[],u=0;return a(t,function(e,t){var o=_(t);o&&(o.onupdate=function(t){n[e]=t},o.oncomplete=function(){u--},u++,o.target=l[e],I([{key:e,setter:function(e){o.target!==e&&(o.resting&&u++,o.target=e)},getter:function(){return n[e]}}],[r,i],n,!0),s.push(o))}),{write:function(e){return s.forEach(function(t){t.interpolate(e)}),0===u},destroy:function(){}}},apis:function(e){var t=e.mixinConfig,n=e.viewProps,r=e.viewExternalAPI;I(t,r,n)}},w=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.paddingTop=parseInt(n.paddingTop,10)||0,e.marginTop=parseInt(n.marginTop,10)||0,e.marginRight=parseInt(n.marginRight,10)||0,e.marginBottom=parseInt(n.marginBottom,10)||0,e.marginLeft=parseInt(n.marginLeft,10)||0,e.left=t.offsetLeft||0,e.top=t.offsetTop||0,e.width=t.offsetWidth||0,e.height=t.offsetHeight||0,e.right=e.left+e.width,e.bottom=e.top+e.height,e.scrollTop=t.scrollTop,e.hidden=null===t.offsetParent,e},D=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.tag,n=void 0===t?"div":t,r=e.name,i=void 0===r?null:r,l=e.attributes,u=void 0===l?{}:l,c=e.read,f=void 0===c?function(){}:c,m=e.write,h=void 0===m?function(){}:m,v=e.create,g=void 0===v?function(){}:v,E=e.destroy,_=void 0===E?function(){}:E,I=e.filterFrameActionsForChild,y=void 0===I?function(e,t){return t}:I,T=e.didCreateView,b=void 0===T?function(){}:T,D=e.ignoreRect,O=void 0!==D&&D,S=e.mixins,A=void 0===S?[]:S;return function(e){var t,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},l=p(n,"filepond--"+i,u),c=window.getComputedStyle(l,null),m=w(),v=null,E=[],I=[],T={},D={},S=[h],M=[f],L=[_],P=function(){return l},C=function(){return[].concat(E)},N=function(){return v||(v=d(m,E,[0,0],[1,1]))},x={element:{get:P},style:{get:function(){return c}},childViews:{get:C}},G=o({},x,{rect:{get:N},ref:{get:function(){return T}},is:function(e){return i===e},appendChild:(t=l,function(e,n){void 0!==n&&t.children[n]?t.insertBefore(e,t.children[n]):t.appendChild(e)}),createChildView:function(e){return function(t,n){return t(e,n)}}(e),appendChildView:function(e,t){return function(e,n){return void 0!==n?t.splice(n,0,e):t.push(e),e}}(0,E),removeChildView:function(e,t){return function(n){return t.splice(t.indexOf(n),1),n.element.parentNode&&e.removeChild(n.element),n}}(l,E),registerWriter:function(e){return S.push(e)},registerReader:function(e){return M.push(e)},dispatch:e.dispatch,query:e.query}),B={element:{get:P},childViews:{get:C},rect:{get:N},isRectIgnored:function(){return O},_read:function(){v=null,E.forEach(function(e){return e._read()}),w(m,l,c),M.forEach(function(e){return e({root:F,props:r,rect:m})})},_write:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=0===t.length;return S.forEach(function(o){!1===o({props:r,root:F,actions:t,timestamp:e})&&(n=!1)}),I.forEach(function(t){!1===t.write(e)&&(n=!1)}),E.filter(function(e){return!!e.element.parentNode}).forEach(function(r){r._write(e,y(r,t))||(n=!1)}),E.filter(function(e){return!e.element.parentNode}).forEach(function(r,o){F.appendChild(r.element,o),r._read(),r._write(e,y(r,t)),n=!1}),n},_destroy:function(){I.forEach(function(e){return e.destroy()}),L.forEach(function(e){return e({root:F})}),E.forEach(function(e){return e._destroy()})}},k=o({},x,{rect:{get:function(){return m}}});a(A,function(e,t){var n=R[e]({mixinConfig:t,viewProps:r,viewState:D,viewInternalAPI:G,viewExternalAPI:B,view:s(k)});n&&I.push(n)});var F=s(G);g({root:F,props:r});var V=l.children.length;return E.forEach(function(e,t){F.appendChild(e.element,V+t)}),b(F),s(B)}},O=function(e){return function(t){var n=t.root,r=t.props,o=t.actions;(void 0===o?[]:o).filter(function(t){return e[t.type]}).forEach(function(t){return e[t.type]({root:n,props:r,action:t.data})})}},S=function(e,t){return t.parentNode.insertBefore(e,t)},A=function(e,t){return t.parentNode.insertBefore(e,t.nextSibling)},M=function(e){return Array.isArray(e)},L=function(e){return e.trim()},P=function(e){return""+e},C=function(e){return"boolean"==typeof e},N=function(e){return C(e)?e:"true"===e},x=function(e){return"string"==typeof e},G=function(e){return v(e)?e:x(e)?P(e).replace(/[a-z]+/gi,""):0},B=function(e){return parseInt(G(e),10)},k=function(e){return v(e)&&isFinite(e)&&Math.floor(e)===e},F=function(e){if(k(e))return e;var t=P(e).trim();return/MB$/i.test(t)?(t=t.replace(/MB$i/,"").trim(),1e3*B(t)*1e3):/KB/i.test(t)?(t=t.replace(/KB$i/,"").trim(),1e3*B(t)):B(t)},V={process:"POST",revert:"DELETE",fetch:"GET",restore:"GET",load:"GET"},U=function(e){return"object"===(void 0===e?"undefined":r(e))&&null!==e},q=function(e){return M(e)?"array":function(e){return null===e}(e)?"null":k(e)?"int":/^[0-9]+ ?(?:GB|MB|KB)$/gi.test(e)?"bytes":function(e){return U(e)&&x(e.url)&&U(e.process)&&U(e.revert)&&U(e.restore)&&U(e.fetch)}(e)?"api":void 0===e?"undefined":r(e)},X={array:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:",";return y(e)?[]:M(e)?e:P(e).split(t).map(L).filter(function(e){return e.length})},boolean:N,int:function(e){return"bytes"===q(e)?F(e):B(e)},float:function(e){return parseFloat(G(e))},bytes:F,string:P,serverapi:function(e){return(n={}).url=x(t=e)?t:t.url||"",n.timeout=t.timeout?parseInt(t.timeout,10):7e3,a(V,function(e){n[e]=function(e,t,n,r){if(null===t)return null;if("function"==typeof t)return t;var o={url:"GET"===n?"?"+e+"=":"",method:n,headers:{},withCredentials:!1,timeout:r};if(x(t))return o.url=t,o;if(Object.assign(o,t),x(o.headers)){var i=o.headers.split(/:(.+)/);o.headers={header:i[0],value:i[1]}}return o.withCredentials=N(o.withCredentials),o}(e,t[e],V[e],n.timeout)}),n;var t,n},function:function(e){return function(e){for(var t=self,n=e.split("."),r=null;r=n.shift();)if(!(t=t[r]))return null;return t}(e)}},z=function(e,t,n){if(e===t)return e;var r,o=q(e);if(o!==n){var i=(r=e,X[n](r));if(o=q(i),null===i)throw'Trying to assign value with incorrect type to "'+option+'", allowed type: "'+n+'"';e=i}return e},Y=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"-";return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(t)},j=function(){return Math.random().toString(36).substr(2,9)},W=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:75;return e.map(function(e,r){return new Promise(function(o,i){setTimeout(function(){t(e),o()},n*r)})})},H=function(e,t){return e.splice(t,1)},$=function(){var e=[],t=function(t,n){H(e,e.findIndex(function(e){return e.event===t&&(e.cb===n||!n)}))};return{fire:function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;oBrowse',te.STRING],labelFileWaitingForSize:["Waiting for size",te.STRING],labelFileSizeNotAvailable:["Size not available",te.STRING],labelFileCountSingular:["file in list",te.STRING],labelFileCountPlural:["files in list",te.STRING],labelFileLoading:["Loading",te.STRING],labelFileAdded:["Added",te.STRING],labelFileRemoved:["Removed",te.STRING],labelFileLoadError:["Error during load",te.STRING],labelFileProcessing:["Uploading",te.STRING],labelFileProcessingComplete:["Upload complete",te.STRING],labelFileProcessingAborted:["Upload cancelled",te.STRING],labelFileProcessingError:["Error during upload",te.STRING],labelTapToCancel:["tap to cancel",te.STRING],labelTapToRetry:["tap to retry",te.STRING],labelTapToUndo:["tap to undo",te.STRING],labelButtonRemoveItem:["Remove",te.STRING],labelButtonAbortItemLoad:["Abort",te.STRING],labelButtonRetryItemLoad:["Retry",te.STRING],labelButtonAbortItemProcessing:["Cancel",te.STRING],labelButtonUndoItemProcessing:["Undo",te.STRING],labelButtonRetryItemProcessing:["Retry",te.STRING],labelButtonProcessItem:["Upload",te.STRING],iconRemove:['',te.STRING],iconProcess:['',te.STRING],iconRetry:['',te.STRING],iconUndo:['',te.STRING],oninit:[null,te.FUNCTION],onwarning:[null,te.FUNCTION],onerror:[null,te.FUNCTION],onaddfilestart:[null,te.FUNCTION],onaddfileprogress:[null,te.FUNCTION],onaddfile:[null,te.FUNCTION],onprocessfilestart:[null,te.FUNCTION],onprocessfileprogress:[null,te.FUNCTION],onprocessfileabort:[null,te.FUNCTION],onprocessfilerevert:[null,te.FUNCTION],onprocessfile:[null,te.FUNCTION],onremovefile:[null,te.FUNCTION],files:[[],te.ARRAY]},se=function(e,t){return y(t)?e[0]||null:k(t)?e[t]||null:("object"===(void 0===t?"undefined":r(t))&&(t=t.id),e.find(function(e){return e.id===t})||null)},ue=function(e){return{GET_ITEM:function(t){return se(e.items,t)},GET_ITEMS:function(t){return[].concat(i(e.items))},GET_ITEM_NAME:function(t){var n=se(e.items,t);return n?n.filename:null},GET_ITEM_SIZE:function(t){var n=se(e.items,t);return n?n.fileSize:null},GET_TOTAL_ITEMS:function(){return e.items.length},IS_ASYNC:function(){return U(e.options.server)&&(U(e.options.server.process)||"function"==typeof e.options.server.process)}}},ce=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return(t+e).slice(-t.length)},fe=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:new Date;return e.getFullYear()+"-"+ce(e.getMonth()+1,"00")+"-"+ce(e.getDate(),"00")+"_"+ce(e.getHours(),"00")+"-"+ce(e.getMinutes(),"00")+"-"+ce(e.getSeconds(),"00")},pe=function(e){return/^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i.test(e)},de=function(e){return e.split("/").pop().split("?").shift()},me=function(e){return e.split(".").pop()},he=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,o="string"==typeof n?e.slice(0,e.size,n):e.slice(0,e.size,e.type);return o.lastModifiedDate=new Date,t&&null===r&&me(t)?o.name=t:(r=r||function(e){if("string"!=typeof e)return"";var t=e.split("/").pop();return/svg/.test(t)?"svg":/zip|compressed/.test(t)?"zip":/plain/.test(t)?"txt":/msword/.test(t)?"doc":/[a-z]+/.test(t)?"jpeg"===t?"jpg":t:""}(o.type),o.name=t+(r?"."+r:"")),o},ve=function(e){return(/^data:(.+);/.exec(e)||[])[1]||null},ge=function(e){var t=ve(e);return function(e,t){for(var n=new ArrayBuffer(e.length),r=new Uint8Array(n),o=0;o=200&&u.status<300?r.onload(Ie("load",u.status,u.response,u.getAllResponseHeaders())):r.onerror(Ie("error",u.status,u.statusText))},u.onerror=function(){r.onerror(Ie("error",u.status,u.statusText))},u.onabort=function(){a||(l=!0,r.onabort())},k(n.timeout)&&(i=setTimeout(function(){a=!0,r.onerror(Ie("error",0,"timeout")),r.abort()},n.timeout)),u.open(n.method,t,!0),Object.keys(n.headers).forEach(function(e){u.setRequestHeader(e,n.headers[e])}),n.responseType&&(u.responseType=n.responseType),n.withCredentials&&(u.withCredentials=!0),u.send(e),r},Te=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1];return"function"==typeof t?t:t&&x(t.url)?function(n,r,i,a,l,s){var u=ye(n,e+t.url,o({},t,{responseType:"blob"}));return u.onload=function(e){e.body=he(e.body,Ee(e.headers)||de(n)||fe()),r(e)},u.onerror=i,u.onprogress=a,u.onabort=l,u.onheaders=s,u}:null},be=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;return e+Math.random()*(t-e)},Re=function(e){var t={complete:!1,perceivedProgress:0,perceivedPerformanceUpdater:null,progress:null,timestamp:null,perceivedDuration:0,duration:0,request:null,response:null},n=function(){t.request&&(t.perceivedPerformanceUpdater.clear(),t.request.abort(),t.complete=!0,r.fire("abort",t.response?t.response.body:null))},r=o({},$(),{process:function(n,o){var i=function(){0!==t.duration&&null!==t.progress&&r.fire("progress",r.getProgress())},a=function(){t.complete=!0,r.fire("load",t.response.body)};r.fire("start"),t.timestamp=Date.now(),t.perceivedPerformanceUpdater=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1e3,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:25,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:250,o=null,i=Date.now();return function a(){var l=Date.now()-i,s=be(n,r);l+s>t&&(s=l+s-t);var u=l/t;u>=1?e(1):(e(u),o=setTimeout(a,s))}(),{clear:function(){clearTimeout(o)}}}(function(e){t.perceivedProgress=e,t.perceivedDuration=Date.now()-t.timestamp,i(),1===e&&t.response&&!t.complete&&a()},be(750,1500)),t.request=e(n,o,function(e){t.response="string"==typeof e?{type:"load",code:200,body:e,headers:{}}:e,t.duration=Date.now()-t.timestamp,t.progress=1,1===t.perceivedProgress&&a()},function(e){t.perceivedPerformanceUpdater.clear(),r.fire("error","string"==typeof e?{type:"error",code:0,body:e}:e)},function(e,n,r){t.duration=Date.now()-t.timestamp,t.progress=e?n/r:null,i()},function(){t.perceivedPerformanceUpdater.clear(),r.fire("abort")})},abort:n,getProgress:function(){return t.progress?Math.min(t.progress,t.perceivedProgress):null},getDuration:function(){return Math.min(t.duration,t.perceivedDuration)},reset:function(){n(),t.complete=!1,t.perceivedProgress=0,t.progress=0,t.timestamp=null,t.perceivedDuration=0,t.duration=0,t.request=null,t.response=null}});return r},we=function(e){return e.substr(0,e.lastIndexOf("."))||e},De={INIT:1,IDLE:2,PROCESSING:3,PROCESSING_PAUSED:4,PROCESSING_COMPLETE:5,PROCESSING_ERROR:6,LOADING:7,LOAD_ERROR:8},Oe=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=j(),n={source:null,file:null,serverFileReference:e,status:e?De.PROCESSING_COMPLETE:De.INIT,activeLoader:null,activeProcessor:null},r={},i=function(e){return n.status=e},a=o({id:{get:function(){return t}},serverId:{get:function(){return n.serverFileReference}},status:{get:function(){return n.status}},filename:{get:function(){return n.file.name}},filenameWithoutExtension:{get:function(){return we(n.file.name)}},fileExtension:{get:function(){return me(n.file.name)}},fileType:{get:function(){return n.file.type}},fileSize:{get:function(){return n.file.size}},file:{get:function(){return n.file}},source:{get:function(){return n.source}},getMetadata:function(e){return e?r[e]:o({},r)},setMetadata:function(e,t){return r[e]=t},abortLoad:function(){n.activeLoader&&n.activeLoader.abort()},retryLoad:function(){n.activeLoader&&n.activeLoader.load()},abortProcessing:function(){n.activeProcessor&&n.activeProcessor.abort()},load:function(e,t,r){n.source=e,n.file=function(e){var t=[e.name,e.size,e.type];return e instanceof Blob||pe(e)?t[0]=fe():pe(e)?(t[1]=e.length,t[2]=ve(e)):e instanceof File||(t[0]=de(e),t[1]=0,t[2]="application/octet-stream"),{name:t[0],size:t[1],type:t[2]}}(e),t.on("init",function(){a.fire("load-init")}),t.on("meta",function(e){n.file.size=e.size,n.file.filename=e.filename,a.fire("load-meta")}),t.on("progress",function(e){i(De.LOADING),a.fire("load-progress",e)}),t.on("error",function(e){i(De.LOAD_ERROR),a.fire("load-request-error",e)}),t.on("abort",function(){i(De.INIT),a.fire("load-abort")}),t.on("load",function(e){n.activeLoader=null;var t=function(e){n.file=e,i(De.IDLE),a.fire("load")};n.serverFileReference?t(e):r(e,t,function(t){n.file=e,a.fire("load-meta"),i(De.LOAD_ERROR),a.fire("load-file-error",t)})}),t.setSource(e),n.activeLoader=t,t.load()},process:function e(t,l){n.file instanceof Blob?(t.on("load",function(e){n.activeProcessor=null,n.serverFileReference=e,i(De.PROCESSING_COMPLETE),a.fire("process-complete",e)}),t.on("start",function(){a.fire("process-start")}),t.on("error",function(e){n.activeProcessor=null,i(De.PROCESSING_ERROR),a.fire("process-error",e)}),t.on("abort",function(e){n.activeProcessor=null,n.serverFileReference=e,i(De.IDLE),a.fire("process-abort")}),t.on("progress",function(e){i(De.PROCESSING),a.fire("process-progress",e)}),l(n.file,function(e){t.process(e,o({},r))},function(e){}),n.activeProcessor=t):a.on("load",function(){e(t,l)})},revert:function(e){null!==n.serverFileReference&&(e(n.serverFileReference,function(){n.serverFileReference=null},function(e){}),i(De.IDLE),a.fire("process-revert"))}},$());return s(a)},Se=function(e,t,n,r,o,i){var a=ye(null,e,{method:"GET",responseType:"blob"});return a.onload=function(n){n.body=he(n.body,Ee(n.headers)||de(e)||fe()),t(n)},a.onerror=n,a.onprogress=r,a.onabort=o,a.onheaders=i,a},Ae=function(e){return 0===e.indexOf("//")&&(e=location.protocol+e),e.toLowerCase().replace(/([a-z])?:\/\//,"$1").split("/")[0]},Me=function(e,t){return function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=n.query,o=n.success,i=void 0===o?function(){}:o,a=n.failure,l=void 0===a?function(){}:a,s=se(e.items,r);s?t(s,i,l):l({error:Ie("error",0,"Item not found"),file:null})}},Le=function(e,t,n){return{ABORT_ALL:function(){t("GET_ITEMS").forEach(function(e){e.abortLoad(),e.abortProcessing()})},DID_SET_FILES:function(t){var r=t.value,a=(void 0===r?[]:r).map(function(e){return{source:e.source?e.source:e,options:e.options}});[].concat(i(n.items)).forEach(function(t){a.find(function(e){return e.source===t.source})||e("REMOVE_ITEM",{query:t})}),a.forEach(function(t,r){[].concat(i(n.items)).find(function(e){return e.source===t.source})||e("ADD_ITEM",o({},t,{interactionMethod:5,index:r}))})},ADD_ITEM:function(r){var i=r.source,a=r.index,l=r.interactionMethod,s=r.success,u=void 0===s?function(){}:s,c=r.failure,f=void 0===c?function(){}:c,p=r.options,d=void 0===p?{}:p;if(y(i))f({error:Ie("error",0,"No source"),file:null});else if(!(i instanceof Blob&&n.options.ignoredFiles.includes(i.name.toLowerCase()))){if(!function(e){var t=e.items.length;if(!e.options.allowMultiple)return 0===t;var n=e.options.maxFiles;return null===n||t=400&&t.code<500)return e("DID_THROW_ITEM_INVALID",{id:_,error:t,status:{main:n.options.labelFileLoadError,sub:t.code+" ("+t.body+")"}}),void f({error:t,file:J(E)});e("DID_THROW_ITEM_LOAD_ERROR",{id:_,error:t,status:{main:n.options.labelFileLoadError,sub:n.options.labelTapToRetry}})}),E.on("load-file-error",function(t){e("DID_THROW_ITEM_INVALID",o({},t,{id:_}))}),E.on("load-abort",function(){e("REMOVE_ITEM",{query:_})}),E.on("load",function(){re("DID_LOAD_ITEM",E,{query:t}).then(function(){e("DID_LOAD_ITEM",{id:_,error:null,serverFileReference:g?i:null}),u(J(E)),h?e("DID_LOAD_LOCAL_ITEM",{id:_}):v?e("DID_COMPLETE_ITEM_PROCESSING",{id:_,error:null,serverFileReference:i}):t("IS_ASYNC")&&n.options.instantUpload&&e("REQUEST_ITEM_PROCESSING",{query:_})})}),E.on("process-start",function(){e("DID_START_ITEM_PROCESSING",{id:_})}),E.on("process-progress",function(t){e("DID_UPDATE_ITEM_PROCESS_PROGRESS",{id:_,progress:t})}),E.on("process-error",function(t){e("DID_THROW_ITEM_PROCESSING_ERROR",{id:_,error:t,status:{main:n.options.labelFileProcessingError,sub:n.options.labelTapToRetry}})}),E.on("process-abort",function(t){n.options.instantUpload?e("REMOVE_ITEM",{query:_}):e("DID_ABORT_ITEM_PROCESSING",{id:_}),e("REVERT_ITEM_PROCESSING",{query:_})}),E.on("process-complete",function(t){e("DID_COMPLETE_ITEM_PROCESSING",{id:_,error:null,serverFileReference:t})}),E.on("process-revert",function(){n.options.instantUpload?e("REMOVE_ITEM",{query:_}):e("DID_REVERT_ITEM_PROCESSING",{id:_})}),e("DID_ADD_ITEM",{id:_,index:a,interactionMethod:l});var I=n.options.server||{},T=I.url,b=I.load,R=I.restore,w=I.fetch;E.load(i,function(e){var t={source:null,complete:!1,progress:0,size:null,timestamp:null,duration:0,request:null},n=function(n){e?(t.timestamp=Date.now(),t.request=e(n,function(e){t.duration=Date.now()-t.timestamp,t.complete=!0,r.fire("load",e instanceof File?e:e.body)},function(e){r.fire("error","string"==typeof e?{type:"error",code:0,body:e}:e)},function(e,n,o){o&&(t.size=o),t.duration=Date.now()-t.timestamp,e?(t.progress=n/o,r.fire("progress",t.progress)):t.progress=null},function(){r.fire("abort")},function(e){r.fire("meta",{size:t.size,filename:Ee("string"==typeof e?e:e.headers)})})):r.fire("error",{type:"error",body:"Can't load URL",code:400})},r=o({},$(),{setSource:function(e){return t.source=e},getProgress:function(){return t.progress},abort:function(){t.request&&t.request.abort()},load:function(){var e,o,i=t.source;r.fire("init",i),i instanceof File?r.fire("load",i):i instanceof Blob?r.fire("load",he(i,fe())):pe(i)?r.fire("load",(e=i,o=fe(),he(ge(e),o,null,void 0))):n(i)}});return r}(g?"limbo"===d.type?Te(T,R):Te(T,b):x(i)&&function(e){return(e.indexOf(":")>-1||e.indexOf("//")>-1)&&Ae(location.href)!==Ae(e)}(i)?Te(T,w):Se),function(e,n,r){re("LOAD_FILE",e,{query:t}).then(n).catch(r)})}},RETRY_ITEM_LOAD:Me(n,function(e){e.retryLoad()}),REQUEST_ITEM_PROCESSING:Me(n,function(t){var n=t.id;e("DID_REQUEST_ITEM_PROCESSING",{id:n}),e("PROCESS_ITEM",{query:t},!0)}),PROCESS_ITEM:Me(n,function(e,r,o){e.onOnce("process-complete",function(){r(J(e))}),e.onOnce("process-error",function(t){o({error:t,file:J(e)})}),e.process(Re(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1],n=arguments[2];return"function"==typeof t?function(){for(var e=arguments.length,r=Array(e),o=0;o0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1];return"function"==typeof t?t:t&&x(t.url)?function(n,r,o){var i=ye(n,e+t.url,t);return i.onload=r,i.onerror=o,i}:function(e,t){return t()}}(n.options.server.url,n.options.server.revert))}),SET_OPTIONS:function(t){var n=t.options;a(n,function(t,n){e("SET_"+Y(t,"_").toUpperCase(),{value:n})})}}},Pe=function(e){return document.createElement(e)},Ce=function(e){return decodeURI(e)},Ne=function(e,t){var n=e.childNodes[0];n?t!==n.nodeValue&&(n.nodeValue=t):(n=document.createTextNode(t),e.appendChild(n))},xe=function(e,t,n,r){var o=(r%360-90)*Math.PI/180;return{x:e+n*Math.cos(o),y:t+n*Math.sin(o)}},Ge=D({tag:"div",name:"progress-indicator",ignoreRect:!0,create:function(e){var t=e.root,n=e.props;n.spin=!1,n.progress=0,n.opacity=0;var r=p("svg");t.ref.path=p("path",{"stroke-width":2,"stroke-linecap":"round"}),r.appendChild(t.ref.path),t.ref.svg=r,t.appendChild(r)},write:function(e){var t=e.root,n=e.props;if(0!==n.opacity){var r=parseInt(u(t.ref.path,"stroke-width"),10),o=.5*t.rect.element.width,i=0,a=0;n.spin?(i=0,a=.5):(i=0,a=n.progress);var l=function(e,t,n,r,o){var i=1;return o>r&&o-r<=.5&&(i=0),r>o&&r-o>=.5&&(i=0),function(e,t,n,r,o,i){var a=xe(e,t,n,o),l=xe(e,t,n,r);return["M",a.x,a.y,"A",n,n,0,i,0,l.x,l.y].join(" ")}(e,t,n,360*Math.min(.9999,r),360*Math.min(.9999,o),i)}(o,o,o-r,i,a);u(t.ref.path,"d",l),u(t.ref.path,"stroke-opacity",n.spin||n.progress>0?1:0)}},mixins:{apis:["progress","spin"],styles:["opacity"],animations:{opacity:{type:"tween",duration:500},progress:{type:"spring",stiffness:.95,damping:.65,mass:10}}}}),Be=D({tag:"button",attributes:{type:"button"},ignoreRect:!0,name:"file-action-button",mixins:{apis:["label"],styles:["translateX","translateY","scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",translateX:"spring",translateY:"spring",opacity:{type:"tween",duration:250}},listeners:!0},create:function(e){var t=e.root,n=e.props;t.element.title=n.label,t.element.innerHTML=n.icon||"",n.disabled=!1},write:function(e){var t=e.root,n=e.props;0!==n.opacity||n.disabled?n.opacity>0&&n.disabled&&(n.disabled=!1,t.element.removeAttribute("disabled")):(n.disabled=!0,u(t.element,"disabled","disabled"))}}),ke=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:".";return(e=Math.round(Math.abs(e)))<1e3?e+" bytes":e-1?function(e,t,n){return e-1===t?10/6:e===t?5:e+1===t?-5:e+2===t?-10/6:0}(t,n.dragIndex):0),e.markedForRemoval||(e.scaleX=1,e.scaleY=1,e.opacity=1),i+=r.outer.height}),t.childViews.filter(function(e){return e.markedForRemoval&&0===e.opacity}).forEach(function(e){t.removeChildView(e),o=!1}),o},tag:"ul",name:"list",filterFrameActionsForChild:function(e,t){return t.filter(function(t){return!t.data||!t.data.id||e.id===t.data.id})},mixins:{apis:["dragIndex"]}}),vt=function(e,t){for(var n=0,r=e.childViews,o=r.length;n3&&void 0!==arguments[3]?arguments[3]:"";n?u(e,t,r):e.removeAttribute(t)},It=function(e){var t=e.root;t.query("GET_TOTAL_ITEMS")>0?_t(t.element,"required",!1):t.query("GET_REQUIRED")&&_t(t.element,"required",!0)},yt=D({tag:"input",name:"browser",ignoreRect:!0,attributes:{type:"file"},create:function(e){var t=e.root,n=e.props;t.element.id="filepond--browser-"+n.id,u(t.element,"aria-controls","filepond--assistant-"+n.id),u(t.element,"aria-labelledby","filepond--drop-label-"+n.id),t.element.addEventListener("change",function(){if(t.element.value){var e=[].concat(i(t.element.files));setTimeout(function(){n.onload(e),function(e){if(e&&""!==e.value){try{e.value=""}catch(e){}if(e.value){var t=Pe("form"),n=e.parentNode,r=e.nextSibling;t.appendChild(e),t.reset(),r?n.insertBefore(e,r):n.appendChild(e)}}}(t.element)},250)}})},write:O({DID_ADD_ITEM:It,DID_REMOVE_ITEM:It,DID_SET_ALLOW_BROWSE:function(e){var t=e.root,n=e.action;_t(t.element,"disabled",!n.value)},DID_SET_ALLOW_MULTIPLE:function(e){var t=e.root,n=e.action;_t(t.element,"multiple",n.value)},DID_SET_ACCEPTED_FILE_TYPES:function(e){var t=e.root,n=e.action;_t(t.element,"accept",!!n.value,n.value?n.value.join(","):"")},DID_SET_CAPTURE_METHOD:function(e){var t=e.root,n=e.action;_t(t.element,"capture",!!n.value,!0===n.value?"":n.value)},DID_SET_REQUIRED:function(e){var t=e.root;e.action.value?0===t.query("GET_TOTAL_ITEMS")&&_t(t.element,"required",!0):_t(t.element,"required",!1)}})}),Tt=D({name:"drop-label",create:function(e){var t=e.root,n=e.props,r=Pe("label");u(r,"for","filepond--browser-"+n.id),u(r,"id","filepond--drop-label-"+n.id),u(r,"aria-hidden","true"),r.addEventListener("keydown",function(e){13!==e.keyCode&&32!==e.keyCode||(e.preventDefault(),t.ref.label.click())}),t.appendChild(r),t.ref.label=r},write:O({DID_SET_LABEL_IDLE:function(e){var t=e.root,n=e.action;e.props.caption=function(e,t){e.innerHTML=t;var n=e.querySelector(".filepond--label-action");return n&&u(n,"tabindex","0"),t}(t.ref.label,n.value)}}),mixins:{apis:["caption"],styles:["opacity","translateX","translateY"],animations:{opacity:{type:"tween",duration:150},translateX:"spring",translateY:"spring"}}}),bt=D({name:"drip-blob",ignoreRect:!0,mixins:{styles:["translateX","translateY","scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",translateX:"spring",translateY:"spring",opacity:{type:"tween",duration:250}}}}),Rt=O({DID_DRAG:function(e){var t=e.root,n=e.action;t.ref.blob?(t.ref.blob.translateX=n.position.scopeLeft,t.ref.blob.translateY=n.position.scopeTop,t.ref.blob.scaleX=1,t.ref.blob.scaleY=1,t.ref.blob.opacity=1):function(e){var t=e.root,n=.5*t.rect.element.width,r=.5*t.rect.element.height;t.ref.blob=t.appendChildView(t.createChildView(bt,{opacity:0,scaleX:2.5,scaleY:2.5,translateX:n,translateY:r}))}({root:t})},DID_DROP:function(e){var t=e.root;t.ref.blob&&(t.ref.blob.scaleX=2.5,t.ref.blob.scaleY=2.5,t.ref.blob.opacity=0)},DID_END_DRAG:function(e){var t=e.root;t.ref.blob&&(t.ref.blob.opacity=0)}}),wt=D({ignoreRect:!0,name:"drip",write:function(e){var t=e.root,n=e.props,r=e.actions;Rt({root:t,props:n,actions:r});var o=t.ref.blob;0===r.length&&o&&0===o.opacity&&(t.removeChildView(o),t.ref.blob=null)}}),Dt=function(e){return new Promise(function(t,n){var r=Nt(e);r.length?t(r):Ot(e).then(t)})},Ot=function(e){return new Promise(function(t,n){var r=(e.items?[].concat(i(e.items)):[]).filter(function(e){return St(e)}).map(function(e){return At(e)});r.length?Promise.all(r).then(function(e){var n=[];e.forEach(function(e){n.push.apply(n,i(e))}),t(n.filter(function(e){return e}))}):t([].concat(i(e.files)))})},St=function(e){if(Pt(e)){var t=Ct(e);if(t)return t.isFile||t.isDirectory}return"file"===e.kind},At=function(e){return new Promise(function(t,n){Lt(e)?Mt(Ct(e)).then(t):t([e.getAsFile()])})},Mt=function(e){return new Promise(function(t,n){var r=[],o=0;!function e(n){n.createReader().readEntries(function(n){n.forEach(function(n){n.isDirectory?e(n):(o++,n.file(function(e){r.push(e),o===r.length&&t(r)}))})})}(e)})},Lt=function(e){return Pt(e)&&(Ct(e)||{}).isDirectory},Pt=function(e){return"webkitGetAsEntry"in e},Ct=function(e){return e.webkitGetAsEntry()},Nt=function(e){var t=[];try{if((t=Gt(e)).length)return t;t=xt(e)}catch(e){}return t},xt=function(e){var t=e.getData("url");return"string"==typeof t&&t.length?[t]:[]},Gt=function(e){var t=e.getData("text/html");if("string"==typeof t&&t.length){var n=t.match(/src\s*=\s*"(.+?)"/);if(n)return[n[1]]}return[]},Bt=[],kt=function(e){return{pageLeft:e.pageX,pageTop:e.pageY,scopeLeft:e.layerX||e.offsetX,scopeTop:e.layerY||e.offsetY}},Ft=function(e){var t=[],n={dragenter:Xt,dragover:zt,dragleave:jt,drop:Yt},r={};a(n,function(n,o){r[n]=o(e,t),e.addEventListener(n,r[n],!1)});var o={element:e,addListener:function(i){return t.push(i),function(){t.splice(t.indexOf(i),1),0===t.length&&(Bt.splice(Bt.indexOf(o),1),a(n,function(t){e.removeEventListener(t,r[t],!1)}))}}};return o},Vt=function(e,t){var n,r=("getRootNode"in(n=t)?n.getRootNode():document).elementFromPoint(e.pageX-window.pageXOffset,e.pageY-window.pageYOffset);return r===t||t.contains(r)},Ut=null,qt=function(e,t){try{e.dropEffect=t}catch(e){}},Xt=function(e,t){return function(e){e.preventDefault(),Ut=e.target,t.forEach(function(t){var n=t.element,r=t.onenter;Vt(e,n)&&(t.state="enter",r(kt(e)))})}},zt=function(e,t){return function(e){e.preventDefault();var n=e.dataTransfer;Dt(n).then(function(r){var o=!1;t.some(function(t){var i=t.filterElement,a=t.element,l=t.onenter,s=t.onexit,u=t.ondrag,c=t.allowdrop;qt(n,"copy");var f=c(r);if(f)if(Vt(e,a)){if(o=!0,null===t.state)return t.state="enter",void l(kt(e));if(t.state="over",i&&!f)return void qt(n,"none");u(kt(e))}else i&&!o&&qt(n,"none"),t.state&&(t.state=null,s(kt(e)));else qt(n,"none")})})}},Yt=function(e,t){return function(e){e.preventDefault();var n=e.dataTransfer;Dt(n).then(function(n){t.forEach(function(t){var r=t.filterElement,o=t.element,i=t.ondrop,a=t.onexit,l=t.allowdrop;t.state=null,l(n)?r&&!Vt(e,o)||i(kt(e),n):a(kt(e))})})}},jt=function(e,t){return function(e){Ut===e.target&&t.forEach(function(t){var n=t.onexit;t.state=null,n(kt(e))})}},Wt=function(e,t,n){e.classList.add("filepond--hopper");var r=n.catchesDropsOnPage,o=n.requiresDropOnElement,i=function(e,t,n){var r=function(e){var t=Bt.find(function(t){return t.element===e});if(t)return t;var n=Ft(e);return Bt.push(n),n}(t),o={element:e,filterElement:n,state:null,ondrop:function(){},onenter:function(){},ondrag:function(){},onexit:function(){},onload:function(){},allowdrop:function(){}};return o.destroy=r.addListener(o),o}(e,r?document.documentElement:e,o),a="",l="";i.allowdrop=function(e){return t(e)},i.ondrop=function(e,n){t(n)?(l="drag-drop",s.onload(n,e)):s.ondragend(e)},i.ondrag=function(e){s.ondrag(e)},i.onenter=function(e){l="drag-over",s.ondragstart(e)},i.onexit=function(e){l="drag-exit",s.ondragend(e)};var s={updateHopperState:function(){a!==l&&(e.dataset.hopperState=l,a=l)},onload:function(){},ondragstart:function(){},ondrag:function(){},ondragend:function(){},destroy:function(){i.destroy()}};return s},Ht=!1,$t=[],Qt=function(e){Dt(e.clipboardData).then(function(e){e.length&&$t.forEach(function(t){return t(e)})})},Zt=null,Jt=null,Kt=[],en=function(e,t){e.element.textContent=t},tn=function(e,t,n){var r=e.query("GET_TOTAL_ITEMS");en(e,n+" "+t+", "+r+" "+(1===r?e.query("GET_LABEL_FILE_COUNT_SINGULAR"):e.query("GET_LABEL_FILE_COUNT_PLURAL"))),clearTimeout(Jt),Jt=setTimeout(function(){!function(e){e.element.textContent=""}(e)},1500)},nn=function(e){return e.element.parentNode.contains(document.activeElement)},rn=function(e){var t=e.root,n=e.action,r=t.query("GET_ITEM",n.id).filename,o=t.query("GET_LABEL_FILE_PROCESSING_ABORTED");en(t,r+" "+o)},on=function(e){var t=e.root,n=e.action,r=t.query("GET_ITEM",n.id).filename;en(t,n.status.main+" "+r+" "+n.status.sub)},an=D({create:function(e){var t=e.root,n=e.props;t.element.id="filepond--assistant-"+n.id,u(t.element,"role","status"),u(t.element,"aria-live","polite"),u(t.element,"aria-relevant","additions")},ignoreRect:!0,write:O({DID_LOAD_ITEM:function(e){var t=e.root,n=e.action;if(nn(t)){t.element.textContent="";var r=t.query("GET_ITEM",n.id);Kt.push(r.filename),clearTimeout(Zt),Zt=setTimeout(function(){tn(t,Kt.join(", "),t.query("GET_LABEL_FILE_ADDED")),Kt.length=0},750)}},DID_REMOVE_ITEM:function(e){var t=e.root,n=e.action;if(nn(t)){var r=n.item;tn(t,r.filename,t.query("GET_LABEL_FILE_REMOVED"))}},DID_COMPLETE_ITEM_PROCESSING:function(e){var t=e.root,n=e.action,r=t.query("GET_ITEM",n.id).filename,o=t.query("GET_LABEL_FILE_PROCESSING_COMPLETE");en(t,r+" "+o)},DID_ABORT_ITEM_PROCESSING:rn,DID_REVERT_ITEM_PROCESSING:rn,DID_THROW_ITEM_LOAD_ERROR:on,DID_THROW_ITEM_INVALID:on,DID_THROW_ITEM_PROCESSING_ERROR:on}),tag:"span",name:"assistant"}),ln=O({DID_SET_ALLOW_BROWSE:function(e){var t=e.root,n=e.props;e.action.value?t.ref.browser=t.appendChildView(t.createChildView(yt,o({},n,{onload:function(e){W(e,function(e){t.dispatch("ADD_ITEM",{interactionMethod:3,source:e,index:0})})}})),0):t.ref.browser&&t.removeChildView(t.ref.browser)},DID_SET_ALLOW_DROP:function(e){var t=e.root,n=(e.props,e.action);if(n.value&&!t.ref.hopper){var r=Wt(t.element,function(e){var n=t.query("GET_ALLOW_REPLACE"),r=t.query("GET_ALLOW_MULTIPLE"),o=t.query("GET_TOTAL_ITEMS"),i=t.query("GET_MAX_TOTAL_ITEMS"),a=e.length;return!(!r&&a>1)&&!(k(i=r?i:n?i:1)&&o+a>i)&&e.every(function(e){return oe("ALLOW_HOPPER_ITEM",e,{query:t.query}).every(function(e){return!0===e})})},{catchesDropsOnPage:t.query("GET_DROP_ON_PAGE"),requiresDropOnElement:t.query("GET_DROP_ON_ELEMENT")});r.onload=function(e,n){var r=t.ref.list.childViews[0],o=vt(r,{left:n.scopeLeft,top:n.scopeTop-t.ref.list.rect.outer.top+t.ref.list.element.scrollTop});W(e,function(e){t.dispatch("ADD_ITEM",{interactionMethod:2,source:e,index:o})}),t.dispatch("DID_DROP",{position:n}),t.dispatch("DID_END_DRAG",{position:n})},r.ondragstart=function(e){t.dispatch("DID_START_DRAG",{position:e})},r.ondrag=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:16,n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],r=Date.now(),o=null;return function(){for(var i=arguments.length,a=Array(i),l=0;le&&(e=n),e},0)}(h),y=f>0?.5*t.rect.element.paddingTop:0;if(E.fixedHeight)u.scalable=!1,u.height=E.fixedHeight+t.rect.element.paddingTop,s.overflow=_>u.height&&c?u.height:null;else if(E.cappedHeight){u.scalable=!0;var T=Math.min(E.cappedHeight,_);t.height=T+y,u.height=Math.min(E.cappedHeight+t.rect.element.paddingTop,I+y),s.overflow=_>u.height&&c?u.height:null}else u.scalable=!0,t.height=_+y+t.rect.element.paddingTop,u.height=I+y}},destroy:function(e){var t=e.root;t.ref.paster&&t.ref.paster.destroy(),t.ref.hopper&&t.ref.hopper.destroy()},mixins:{styles:["height"]}}),un=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=null,n=ae(),l=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=o({},e),i=[],a=[],l=function(e,t,n){n?a.push({type:e,data:t}):(f[e]&&f[e](t),i.push({type:e,data:t}))},s=function(e){for(var t,n=arguments.length,r=Array(n>1?n-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(function(n,r){l.dispatch("ADD_ITEM",{interactionMethod:1,source:e,index:t.index,success:n,failure:r})})},E=function(e){return l.dispatch("REMOVE_ITEM",{query:e}),null===l.query("GET_ITEM",e)},_=function(){return l.query("GET_ITEMS")},I=function(e){return new Promise(function(t,n){l.dispatch("PROCESS_ITEM",{query:e,success:t,failure:n})})},y=o({},$(),p,function(e,t){var n={};return a(t,function(t){n[t]={get:function(){return e.getState().options[t]},set:function(n){e.dispatch("SET_"+Y(t,"_").toUpperCase(),{value:n})}}}),n}(l,n),{setOptions:function(e){return l.dispatch("SET_OPTIONS",{options:e})},addFile:g,addFiles:function(){for(var e=arguments.length,t=Array(e),n=0;n0&&void 0!==arguments[0]?arguments[0]:{},t={};return a(ae(),function(e,n){t[e]=n[0]}),un(o({},t,e))},fn=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=[].concat(i(e.attributes)).reduce(function(t,n){return t[function(e){return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"-";return e.replace(new RegExp(t+".","g"),function(e){return e.charAt(1).toUpperCase()})}(e.replace(/^data-/,""))}(n.name)]=u(e,n.name),t},{});return function e(t,n){a(n,function(n,r){a(t,function(e,o){var i=new RegExp(n);if(i.test(e)&&(delete t[e],!1!==r))if(x(r))t[r]=o;else{var a,l=r.group;U(r)&&!t[l]&&(t[l]={}),t[l][(a=e.replace(i,""),a.charAt(0).toLowerCase()+a.slice(1))]=o}}),r.mapping&&e(t[r.group],r.mapping)})}(n,t),n},pn=["fire","_read","_write"],dn=function(e){var t={};return Q(e,t,pn),t},mn=function(e,t){return e.replace(/(?:{([a-z]+)})/g,function(e,n){return t[n]})},hn=["jpg","jpeg","png","gif","bmp","webp","svg","tiff"],vn=["css","csv","html","txt"],gn={zip:"zip|compressed",epub:"application/epub+zip"},En=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";return e=e.toLowerCase(),hn.includes(e)?"image/"+("jpg"===e?"jpeg":"svg"===e?"svg+xml":e):vn.includes(e)?"text/"+e:gn[e]||null},_n=function(e){var t=new Blob(["(",e.toString(),")()"],{type:"application/javascript"}),n=URL.createObjectURL(t),r=new Worker(n);return{transfer:function(e,t){},post:function(e,t,n){var o=j();r.onmessage=function(e){e.data.id===o&&t(e.data.message)},r.postMessage({id:o,message:e},n)},terminate:function(){r.terminate(),URL.revokeObjectURL(n)}}},In=function(e,t){return new Promise(function(t,n){var r=new Image;r.onload=function(){t(r)},r.onerror=function(e){n(e)},r.src=e})},yn=function(e){return _e(e,e.name)},Tn=[],bn=function(e){var t;Tn.includes(e)||(Tn.push(e),t=e({addFilter:ie,utils:{Type:te,forin:a,isString:x,toNaturalFileSize:ke,replaceInString:mn,getExtensionFromFilename:me,getFilenameWithoutExtension:we,guesstimateMimeType:En,getFileFromBlob:he,getFilenameFromURL:de,createRoute:O,createWorker:_n,createView:D,loadImage:In,copyFile:yn,renameFile:_e,applyFilterChain:re}}).options,Object.assign(le,t))},Rn={apps:[]},wn="undefined"!=typeof navigator;if(wn&&function(e){var t=1e3/(arguments.length>1&&void 0!==arguments[1]?arguments[1]:60),n=null;!function r(o){window.requestAnimationFrame(r),n||(n=o);var i=o-n;i<=t||(n=o-i%t,e(o))}(performance.now())}((Ke=Rn.apps,et="_read",tt="_write",function(e){Ke.forEach(function(e){return e[et]()}),Ke.forEach(function(t){return t[tt](e)})}),60),wn){var Dn=function e(){document.dispatchEvent(new CustomEvent("FilePond:loaded",{detail:{supported:Nn,create:Mn,destroy:Ln,parse:Pn,find:Cn,registerPlugin:xn,setOptions:Bn}})),document.removeEventListener("DOMContentLoaded",e)};"loading"!==document.readyState?setTimeout(function(){return Dn()},0):document.addEventListener("DOMContentLoaded",Dn)}var On=function(){return a(ae(),function(e,t){An[e]=t[1]})},Sn=o({},De),An={};On();var Mn=function(){var e=function(){return(arguments.length<=0?void 0:arguments[0])instanceof HTMLElement?function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={"^class$":"className","^multiple$":"allowMultiple","^capture$":"captureMethod","^server":{group:"server",mapping:{"^process":{group:"process"},"^revert":{group:"revert"},"^fetch":{group:"fetch"},"^restore":{group:"restore"},"^load":{group:"load"}}},"^type$":!1,"^files$":!1};oe("SET_ATTRIBUTE_TO_OPTION_MAP",n);var r=o({},fn("FIELDSET"===e.nodeName?e.querySelector("input[type=file]"):e,n),t);r.files=(t.files||[]).concat([].concat(i(e.querySelectorAll("input:not([type=file])"))).map(function(e){return{source:e.value,options:{type:e.dataset.type}}}));var a=cn(r);return e.files&&[].concat(i(e.files)).forEach(function(e){a.addFile(e)}),a.replaceElement(e),a}.apply(void 0,arguments):cn.apply(void 0,arguments)}.apply(void 0,arguments);return e.on("destroy",Ln),Rn.apps.push(e),dn(e)},Ln=function(e){var t=Rn.apps.findIndex(function(t){return t.isAttachedTo(e)});return t>=0&&(Rn.apps.splice(t,1)[0].restoreElement(),!0)},Pn=function(e){return[].concat(i(e.querySelectorAll(".filepond"))).filter(function(e){return!Rn.apps.find(function(t){return t.isAttachedTo(e)})}).map(function(e){return Mn(e)})},Cn=function(e){var t=Rn.apps.find(function(t){return t.isAttachedTo(e)});return t?dn(t):null},Nn=function(){return!!wn&&!!("[object OperaMini]"!==Object.prototype.toString.call(window.operamini)&&"visibilityState"in document&&"Promise"in window&&"slice"in Blob.prototype&&"URL"in window&&"createObjectURL"in window.URL&&"performance"in window)},xn=function(){for(var e=arguments.length,t=Array(e),n=0;n=5&&l<=8){var f=[c,u];u=f[0],c=f[1]}var p=a.getMetadata("crop")||{rect:{x:0,y:0,width:1,height:1},aspectRatio:c/u},d=window.devicePixelRatio,m=n.query("GET_IMAGE_PREVIEW_HEIGHT"),h=n.query("GET_IMAGE_PREVIEW_MIN_HEIGHT"),v=n.query("GET_IMAGE_PREVIEW_MAX_HEIGHT"),g=n.rect.inner.width,E=c/u,_=g,I=g*E,y=null!==m?m:Math.max(h,Math.min(c,v)),T=y/E,b=function(e,n,r,o){if(n=Math.round(n),r=Math.round(r),o>=5&&o<=8){var i=[r,n];n=i[0],r=i[1]}var a=document.createElement("canvas"),l=a.getContext("2d");return o>=5&&o<=8?(a.width=r,a.height=n):(a.width=n,a.height=r),l.save(),t(l,n,r,o),l.drawImage(e,0,0,n,r),l.restore(),"close"in e&&e.close(),a}(o.data,T*d,y*d,l),R=null!==m?m:Math.max(h,Math.min(g*p.aspectRatio,v)),w=R/p.aspectRatio;w>_&&(R=(w=_)*p.aspectRatio);var D=R/(I*p.rect.height);u=_*D,c=I*D;var O=-p.rect.x*_*D,S=-p.rect.y*I*D;n.ref.clip.style.cssText="\n width: "+Math.round(w)+"px;\n height: "+Math.round(R)+"px;\n ",b.style.cssText="\n width: "+Math.round(u)+"px;\n height: "+Math.round(c)+"px;\n transform: translate("+Math.round(O)+"px, "+Math.round(S)+"px) rotateZ(0.00001deg);\n ",n.ref.clip.appendChild(b),n.dispatch("DID_IMAGE_PREVIEW_DRAW",{id:i})}}),mixins:{styles:["scaleX","scaleY","opacity"],animations:{scaleX:n,scaleY:n,opacity:{type:"tween",duration:750}}}})},o=function(){self.onmessage=function(t){e(t.data.message,function(e){self.postMessage({id:t.data.id,message:e},[e])})};var e=function(e,t){fetch(e.file).then(function(e){return e.blob()}).then(function(e){return createImageBitmap(e)}).then(function(e){return t(e)})}},i=function(e){return-.5*(Math.cos(Math.PI*e)-1)},a=function(e,t,n,r,o){e.width=t,e.height=n;var a=e.getContext("2d"),l=.5*t,s=a.createRadialGradient(l,n+110,n-100,l,n+110,n+100);!function(e,t){for(var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:i,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:10,a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0,l=1-a,s=t.join(","),u=0;u<=o;u++){var c=u/o,f=a+l*c;e.addColorStop(f,"rgba("+s+", "+r(c)*n+")")}}(s,r,o,void 0,8,.4),a.save(),a.translate(.5*-t,0),a.scale(2,1),a.fillStyle=s,a.fillRect(0,0,t,n),a.restore()},l="undefined"!=typeof navigator,s=l&&document.createElement("canvas"),u=l&&document.createElement("canvas"),c=l&&document.createElement("canvas");l&&(a(s,500,200,[40,40,40],.85),a(u,500,200,[196,78,71],1),a(c,500,200,[54,151,99],1));var f=function(e){var t=function(e){return e.utils.createView({name:"image-preview-overlay",tag:"canvas",ignoreRect:!0,create:function(e){var t,n,r=e.root;t=e.props.template,(n=r.element).width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0)},mixins:{styles:["opacity"],animations:{opacity:{type:"spring",mass:25}}}})}(e),n=function(e){var t=e.root;t.ref.overlayShadow.opacity=1,t.ref.overlayError.opacity=0,t.ref.overlaySuccess.opacity=0},i=function(e){var t=e.root;t.ref.overlayShadow.opacity=.25,t.ref.overlayError.opacity=1};return e.utils.createView({name:"image-preview-wrapper",create:function(n){var o=n.root,i=n.props,a=r(e);o.ref.image=o.appendChildView(o.createChildView(a,{id:i.id,scaleX:1.25,scaleY:1.25,opacity:0})),o.ref.overlayShadow=o.appendChildView(o.createChildView(t,{template:s,opacity:0})),o.ref.overlaySuccess=o.appendChildView(o.createChildView(t,{template:c,opacity:0})),o.ref.overlayError=o.appendChildView(o.createChildView(t,{template:u,opacity:0}))},write:e.utils.createRoute({DID_IMAGE_PREVIEW_LOAD:function(e){e.root.ref.overlayShadow.opacity=1},DID_IMAGE_PREVIEW_DRAW:function(e){var t=e.root.ref.image;t.scaleX=1,t.scaleY=1,t.opacity=1},DID_IMAGE_PREVIEW_CONTAINER_CREATE:function(t){var n,r,i,a=t.root,l=t.props,s=e.utils,u=(s.createView,s.createWorker),c=s.loadImage,f=l.id,p=a.query("GET_ITEM",f),d=URL.createObjectURL(p.file),m=function(e,t,n,r){c(d).then(h)},h=function(e){URL.revokeObjectURL(d),a.dispatch("DID_IMAGE_PREVIEW_LOAD",{id:f,data:e})};n=d,r=function(e,t){if(a.dispatch("DID_IMAGE_PREVIEW_CALCULATE_SIZE",{id:f,width:e,height:t}),"createImageBitmap"in window){var n=u(o);n.post({file:d},function(e){n.terminate(),e?h(e):m()})}else m()},(i=new Image).onload=function(){var e=i.naturalWidth,t=i.naturalHeight;i=null,r(e,t)},i.src=n},DID_THROW_ITEM_LOAD_ERROR:i,DID_THROW_ITEM_PROCESSING_ERROR:i,DID_THROW_ITEM_INVALID:i,DID_COMPLETE_ITEM_PROCESSING:function(e){var t=e.root;t.ref.overlayShadow.opacity=.25,t.ref.overlaySuccess.opacity=1},DID_START_ITEM_PROCESSING:n,DID_REVERT_ITEM_PROCESSING:n})})},p=function(e){var t=e.addFilter,n=e.utils,r=n.Type,o=n.createRoute,i=f(e);return t("CREATE_VIEW",function(e){var t=e.is,n=e.view,r=e.query;t("file")&&r("GET_ALLOW_IMAGE_PREVIEW")&&n.registerWriter(o({DID_LOAD_ITEM:function(e){var t=e.root,o=e.props.id,a=r("GET_ITEM",o);if(a){var l=a.file;if(function(e){return/^image/.test(e.type)&&!/svg/.test(e.type)}(l)){var s="createImageBitmap"in(window||{}),u=r("GET_IMAGE_PREVIEW_MAX_FILE_SIZE");!s&&u&&l.size>u||(t.ref.imagePreview=n.appendChildView(n.createChildView(i,{id:o})),t.dispatch("DID_IMAGE_PREVIEW_CONTAINER_CREATE",{id:o}))}}},DID_IMAGE_PREVIEW_CALCULATE_SIZE:function(e){var t=e.root,n=e.props,r=e.action,o=t.query("GET_ITEM",{id:n.id}),i=(o.getMetadata("exif")||{}).orientation||-1,a=r.width,l=r.height;if(i>=5&&i<=8){var s=[l,a];a=s[0],l=s[1]}var u=o.getMetadata("crop")||{rect:{x:0,y:0,width:1,height:1},aspectRatio:l/a},c=t.query("GET_IMAGE_PREVIEW_HEIGHT"),f=t.query("GET_IMAGE_PREVIEW_MIN_HEIGHT"),p=t.query("GET_IMAGE_PREVIEW_MAX_HEIGHT");(a=(l=null!==c?c:Math.max(f,Math.min(l,p)))/u.aspectRatio)>t.rect.element.width&&(l=(a=t.rect.element.width)*u.aspectRatio),t.ref.imagePreview.element.style.cssText="height:"+Math.round(l)+"px"}}))}),{options:{allowImagePreview:[!0,r.BOOLEAN],imagePreviewHeight:[null,r.INT],imagePreviewMinHeight:[44,r.INT],imagePreviewMaxHeight:[256,r.INT],imagePreviewMaxFileSize:[null,r.INT]}}};return"undefined"!=typeof navigator&&document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:p})),p},"object"===("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)&&void 0!==e?e.exports=i():void 0===(o="function"==typeof(r=i)?r.call(t,n,t,e):r)||(e.exports=o)},3:function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n,r=e[1]||"",o=e[3];if(!o)return r;if(t&&"function"==typeof btoa){var i=(n=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),a=o.sources.map(function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"});return[r].concat(a).concat([i]).join("\n")}return[r].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;o=0&&c.splice(t,1)}function v(e){var t=document.createElement("style");return e.attrs.type="text/css",g(t,e.attrs),m(e,t),t}function g(e,t){Object.keys(t).forEach(function(n){e.setAttribute(n,t[n])})}function E(e,t){var n,r,o,i;if(t.transform&&e.css){if(!(i=t.transform(e.css)))return function(){};e.css=i}if(t.singleton){var a=u++;n=s||(s=v(t)),r=y.bind(null,n,a,!1),o=y.bind(null,n,a,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",g(t,e.attrs),m(e,t),t}(t),r=function(e,t,n){var r=n.css,o=n.sourceMap,i=void 0===t.convertToAbsoluteUrls&&o;(t.convertToAbsoluteUrls||i)&&(r=f(r)),o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var a=new Blob([r],{type:"text/css"}),l=e.href;e.href=URL.createObjectURL(a),l&&URL.revokeObjectURL(l)}.bind(null,n,t),o=function(){h(n),n.href&&URL.revokeObjectURL(n.href)}):(n=v(t),r=function(e,t){var n=t.css,r=t.media;if(r&&e.setAttribute("media",r),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){h(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=a()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=d(e,t);return p(n,t),function(e){for(var r=[],o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o2&&(e.pop(),e.shift()),e}},methods:{updateValue:function(e,t){var n=[].concat(o(this.selection));n.includes(e)?n.splice(n.indexOf(e),1):n.push(e),n.sort(),n=n.join(","),this.options.wrap&&n.length>0&&(n=","+n+","),"CSV"===this.type&&(n=n.split(",")),this.$emit("input",n)}}},a=n(0),s=Object(a.a)(i,function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"interface-checkboxes"},e._l(e.options.choices,function(t,r){return n("v-checkbox",{key:t,attrs:{id:t,value:r,disabled:e.readonly,label:t,checked:e.selection.includes(r)},on:{change:function(t){e.updateValue(r,t)}}})}))},[],!1,function(e){n(159)},"data-v-1667bed0",null);t.default=s.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/checkboxes/Readonly.js b/public/extensions/core/interfaces/checkboxes/Readonly.js new file mode 100644 index 0000000000..fbb5c2a95b --- /dev/null +++ b/public/extensions/core/interfaces/checkboxes/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=77)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,s,u){var l=typeof(e=e||{}).default;"object"!==l&&"function"!==l||(e=e.default);var a,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),s?(a=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(s)},c._ssrRegister=a):o&&(a=u?function(){o.call(this,this.$root.$options.shadowRoot)}:o),a)if(c.functional){c._injectStyles=a;var p=c.render;c.render=function(e,t){return a.call(t),p(e,t)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,a):[a]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},77:function(e,t,n){"use strict";n.r(t);var r=n(1),o={name:"readonly-checkboxes",mixins:[n.n(r).a],computed:{selection:function(){if(null==this.value)return[];var e="VARCHAR"===this.type?this.value.split(","):this.value;return this.options.wrap&&(e.pop(),e.shift()),e},displayValue:function(){var e=this;return this.options.formatting?this.selection.map(function(t){return e.options.choices[t]}).join(", "):this.selection.join(", ")}}},i=n(0),s=Object(i.a)(o,function(){var e=this.$createElement;return(this._self._c||e)("div",{staticClass:"readonly-checkboxes"},[this._v(this._s(this.displayValue))])},[],!1,null,null,null);t.default=s.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/checkboxes/meta.json b/public/extensions/core/interfaces/checkboxes/meta.json new file mode 100644 index 0000000000..720a7a3b55 --- /dev/null +++ b/public/extensions/core/interfaces/checkboxes/meta.json @@ -0,0 +1,49 @@ +{ + "name": "$t:checkboxes", + "version": "1.0.0", + "datatypes": { + "CSV": null, + "VARCHAR": 255 + }, + "fieldset": true, + "options": { + "choices": { + "name": "$t:choices", + "comment": "$t:choices_comment", + "interface": "json", + "type": "JSON", + "default": { + "value1": "$t:option 1", + "value2": "$t:option 2" + } + }, + "wrap": { + "name": "$t:wrap", + "comment": "$t:wrap_comment", + "interface": "toggle", + "type": "BOOLEAN", + "default": true + }, + "formatting": { + "name": "$t:formatting", + "comment": "$t:formatting_comment", + "interface": "toggle", + "type": "BOOLEAN", + "default": true + } + }, + "translation": { + "en-US": { + "checkboxes": "Checkboxes", + "choices": "choices", + "choices_comment": "Enter JSON key value pairs with the saved value and text displayed.", + "wrap": "Wrap with Delimiter", + "wrap_comment": "Wrap the saved value in a delimiter (improves searchability).", + "option": "Option", + "formatting": "Show display text", + "formatting_comment": "Render the values as the display values", + "display_text": "Display Text", + "value": "Value" + } + } +} diff --git a/public/extensions/core/interfaces/color/Interface.js b/public/extensions/core/interfaces/color/Interface.js new file mode 100644 index 0000000000..0c2b2c8fc6 --- /dev/null +++ b/public/extensions/core/interfaces/color/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(t){var e={};function r(a){if(e[a])return e[a].exports;var n=e[a]={i:a,l:!1,exports:{}};return t[a].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=t,r.c=e,r.d=function(t,e,a){r.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:a})},r.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=74)}({0:function(t,e,r){"use strict";function a(t,e,r,a,n,o,l,i){var s=typeof(t=t||{}).default;"object"!==s&&"function"!==s||(t=t.default);var u,c="function"==typeof t?t.options:t;if(e&&(c.render=e,c.staticRenderFns=r,c._compiled=!0),a&&(c.functional=!0),o&&(c._scopeId=o),l?(u=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),n&&n.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(l)},c._ssrRegister=u):n&&(u=i?function(){n.call(this,this.$root.$options.shadowRoot)}:n),u)if(c.functional){c._injectStyles=u;var h=c.render;c.render=function(t,e){return u.call(e),h(t,e)}}else{var d=c.beforeCreate;c.beforeCreate=d?[].concat(d,u):[u]}return{exports:t,options:c}}r.d(e,"a",function(){return a})},1:function(t,e){t.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},15:function(t,e,r){var a=r(8);function n(t){var e=function(){for(var t={},e=Object.keys(a),r=e.length,n=0;n1&&(e=Array.prototype.slice.call(arguments));var r=t(e);if("object"===(void 0===r?"undefined":a(r)))for(var n=r.length,o=0;o1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(n)})}),t.exports=l},17:function(t,e,r){"use strict";t.exports=function(t){return!(!t||"string"==typeof t)&&(t instanceof Array||Array.isArray(t)||t.length>=0&&(t.splice instanceof Function||Object.getOwnPropertyDescriptor(t,t.length-1)&&"String"!==t.constructor.name))}},18:function(t,e,r){"use strict";var a=r(17),n=Array.prototype.concat,o=Array.prototype.slice,l=t.exports=function(t){for(var e=[],r=0,l=t.length;r=4&&1!==t[3]&&(e=", "+t[3]),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+e+")"},i.to.keyword=function(t){return o[t.slice(0,3)]}},2:function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t,e){for(var r=[],a={},n=0;nr.parts.length&&(a.parts.length=r.parts.length)}else{var o=[];for(n=0;n>16&255,t>>8&255,255&t],this.valpha=1;else{this.valpha=1;var f=Object.keys(t);"alpha"in t&&(f.splice(f.indexOf("alpha"),1),this.valpha="number"==typeof t.alpha?t.alpha:0);var v=f.sort().join("");if(!(v in i))throw new Error("Unable to parse color from object: "+JSON.stringify(t));this.model=i[v];var b=n[this.model].labels,g=[];for(r=0;rr?(e+.05)/(r+.05):(r+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},isDark:function(){var t=this.rgb().color;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},isLight:function(){return!this.isDark()},negate:function(){for(var t=this.rgb(),e=0;e<3;e++)t.color[e]=255-t.color[e];return t},lighten:function(t){var e=this.hsl();return e.color[2]+=e.color[2]*t,e},darken:function(t){var e=this.hsl();return e.color[2]-=e.color[2]*t,e},saturate:function(t){var e=this.hsl();return e.color[1]+=e.color[1]*t,e},desaturate:function(t){var e=this.hsl();return e.color[1]-=e.color[1]*t,e},whiten:function(t){var e=this.hwb();return e.color[1]+=e.color[1]*t,e},blacken:function(t){var e=this.hwb();return e.color[2]+=e.color[2]*t,e},grayscale:function(){var t=this.rgb().color,e=.3*t[0]+.59*t[1]+.11*t[2];return u.rgb(e,e,e)},fade:function(t){return this.alpha(this.valpha-this.valpha*t)},opaquer:function(t){return this.alpha(this.valpha+this.valpha*t)},rotate:function(t){var e=this.hsl(),r=e.color[0];return r=(r=(r+t)%360)<0?360+r:r,e.color[0]=r,e},mix:function(t,e){var r=t.rgb(),a=this.rgb(),n=void 0===e?.5:e,o=2*n-1,l=r.alpha()-a.alpha(),i=((o*l==-1?o:(o+l)/(1+o*l))+1)/2,s=1-i;return u.rgb(i*r.red()+s*a.red(),i*r.green()+s*a.green(),i*r.blue()+s*a.blue(),r.alpha()*n+a.alpha()*(1-n))}},Object.keys(n).forEach(function(t){if(-1===l.indexOf(t)){var e=n[t].channels;u.prototype[t]=function(){if(this.model===t)return new u(this);if(arguments.length)return new u(arguments,t);var r,a="number"==typeof arguments[e]?e:this.valpha;return new u((r=n[this.model][t].raw(this.color),Array.isArray(r)?r:[r]).concat(a),t)},u[t]=function(r){return"number"==typeof r&&(r=d(o.call(arguments),e)),new u(r,t)}}}),t.exports=u},74:function(t,e,r){"use strict";r.r(e);var a=r(1),n=r.n(a),o=r(6),l=r.n(o),i={name:"interface-color",mixins:[n.a],data:function(){return{rawValue:null}},computed:{color:function(){try{return"hex"===this.options.input?l()(this.rawValue):l.a[this.options.input](this.rawValue)}catch(t){return null}},palette:function(){if(this.options.palette)return(Array.isArray(this.options.palette)?this.options.palette:this.options.palette.split(",")).map(function(t){return l()(t)})}},created:function(){this.setDefault()},watch:{rawValue:function(){if(null===this.color)return this.$emit("input",null);var t=void 0;t="hex"===this.options.output?this.color.hex():(t=this.color[this.options.output]().array()).map(function(e,r){return r===t.length-1?Math.round(100*e)/100:Math.round(e)}),this.$emit("input",t)},options:{deep:!0,handler:function(){this.setDefault()}}},methods:{setDefault:function(){var t=l()(this.value||"#000");this.setRawValue(t)},setRawValue:function(t){return"hex"===this.options.input?this.rawValue=t.hex():this.rawValue=t[this.options.input]().array()}}},s=r(0),u=Object(s.a)(i,function(){var t=this,e=t.$createElement,r=t._self._c||e;return r("div",{staticClass:"interface-color"},[t.options.paletteOnly||"hex"!==t.options.input||!1!==t.readonly?t.options.paletteOnly||"rgb"!==t.options.input||!1!==t.readonly?t.options.paletteOnly||"hsl"!==t.options.input||!1!==t.readonly?t.options.paletteOnly||"cmyk"!==t.options.input||!1!==t.readonly?t._e():r("div",{staticClass:"sliders"},[r("label",{staticClass:"slider-label"},[t._v("C")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:100,alwaysShowOutput:!0},model:{value:t.rawValue[0],callback:function(e){t.$set(t.rawValue,0,e)},expression:"rawValue[0]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("M")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:100,alwaysShowOutput:!0},model:{value:t.rawValue[1],callback:function(e){t.$set(t.rawValue,1,e)},expression:"rawValue[1]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("Y")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:100,alwaysShowOutput:!0},model:{value:t.rawValue[2],callback:function(e){t.$set(t.rawValue,2,e)},expression:"rawValue[2]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("K")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:100,alwaysShowOutput:!0},model:{value:t.rawValue[3],callback:function(e){t.$set(t.rawValue,3,e)},expression:"rawValue[3]"}}),r("br"),t._v(" "),t.options.allowAlpha?r("label",{staticClass:"slider-label"},[t._v("A")]):t._e(),t._v(" "),t.options.allowAlpha?r("v-slider",{staticClass:"slider",attrs:{min:0,max:1,step:.01,alwaysShowOutput:!0},model:{value:t.rawValue[4],callback:function(e){t.$set(t.rawValue,4,e)},expression:"rawValue[4]"}}):t._e()],1):r("div",{staticClass:"sliders"},[r("label",{staticClass:"slider-label"},[t._v("H")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:360,alwaysShowOutput:!0},model:{value:t.rawValue[0],callback:function(e){t.$set(t.rawValue,0,e)},expression:"rawValue[0]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("S")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:100,alwaysShowOutput:!0},model:{value:t.rawValue[1],callback:function(e){t.$set(t.rawValue,1,e)},expression:"rawValue[1]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("L")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:100,alwaysShowOutput:!0},model:{value:t.rawValue[2],callback:function(e){t.$set(t.rawValue,2,e)},expression:"rawValue[2]"}}),r("br"),t._v(" "),t.options.allowAlpha?r("label",{staticClass:"slider-label"},[t._v("A")]):t._e(),t._v(" "),t.options.allowAlpha?r("v-slider",{staticClass:"slider",attrs:{min:0,max:1,step:.01,alwaysShowOutput:!0},model:{value:t.rawValue[3],callback:function(e){t.$set(t.rawValue,3,e)},expression:"rawValue[3]"}}):t._e()],1):r("div",{staticClass:"sliders"},[r("label",{staticClass:"slider-label"},[t._v("R")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:256,alwaysShowOutput:!0},model:{value:t.rawValue[0],callback:function(e){t.$set(t.rawValue,0,e)},expression:"rawValue[0]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("G")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:256,alwaysShowOutput:!0},model:{value:t.rawValue[1],callback:function(e){t.$set(t.rawValue,1,e)},expression:"rawValue[1]"}}),r("br"),t._v(" "),r("label",{staticClass:"slider-label"},[t._v("B")]),t._v(" "),r("v-slider",{staticClass:"slider",attrs:{min:0,max:256,alwaysShowOutput:!0},model:{value:t.rawValue[2],callback:function(e){t.$set(t.rawValue,2,e)},expression:"rawValue[2]"}}),r("br"),t._v(" "),t.options.allowAlpha?r("label",{staticClass:"slider-label"},[t._v("A")]):t._e(),t._v(" "),t.options.allowAlpha?r("v-slider",{staticClass:"slider",attrs:{min:0,max:1,step:.01,alwaysShowOutput:!0},model:{value:t.rawValue[3],callback:function(e){t.$set(t.rawValue,3,e)},expression:"rawValue[3]"}}):t._e()],1):r("div",{staticClass:"input"},[t.options.allowAlpha?r("v-input",{attrs:{type:"text",placeholder:"#3498dbee",pattern:"[#0-9a-fA-F]",maxlength:9},model:{value:t.rawValue,callback:function(e){t.rawValue=e},expression:"rawValue"}}):r("v-input",{attrs:{type:"text",placeholder:"#3498db",pattern:"[#0-9a-fA-F]",maxlength:7},model:{value:t.rawValue,callback:function(e){t.rawValue=e},expression:"rawValue"}})],1),t._v(" "),r("div",{staticClass:"swatch",style:"background-color: "+(t.color?t.color.hex():"transparent")},[r("i",{staticClass:"material-icons"},[t._v("check")])]),t._v(" "),t._l(t.palette,function(e){return!1===t.readonly?r("button",{style:{borderColor:e,color:e,backgroundColor:e},on:{click:function(r){t.setRawValue(e)}}},[r("i",{staticClass:"material-icons"},[t._v("colorize")])]):t._e()})],2)},[],!1,function(t){r(157)},"data-v-de93d27a",null);e.default=u.exports},8:function(t,e,r){var a=r(9),n={};for(var o in a)a.hasOwnProperty(o)&&(n[a[o]]=o);var l=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var i in l)if(l.hasOwnProperty(i)){if(!("channels"in l[i]))throw new Error("missing channels property: "+i);if(!("labels"in l[i]))throw new Error("missing channel labels property: "+i);if(l[i].labels.length!==l[i].channels)throw new Error("channel and label counts mismatch: "+i);var s=l[i].channels,u=l[i].labels;delete l[i].channels,delete l[i].labels,Object.defineProperty(l[i],"channels",{value:s}),Object.defineProperty(l[i],"labels",{value:u})}l.rgb.hsl=function(t){var e,r,a=t[0]/255,n=t[1]/255,o=t[2]/255,l=Math.min(a,n,o),i=Math.max(a,n,o),s=i-l;return i===l?e=0:a===i?e=(n-o)/s:n===i?e=2+(o-a)/s:o===i&&(e=4+(a-n)/s),(e=Math.min(60*e,360))<0&&(e+=360),r=(l+i)/2,[e,100*(i===l?0:r<=.5?s/(i+l):s/(2-i-l)),100*r]},l.rgb.hsv=function(t){var e,r,a=t[0],n=t[1],o=t[2],l=Math.min(a,n,o),i=Math.max(a,n,o),s=i-l;return r=0===i?0:s/i*1e3/10,i===l?e=0:a===i?e=(n-o)/s:n===i?e=2+(o-a)/s:o===i&&(e=4+(a-n)/s),(e=Math.min(60*e,360))<0&&(e+=360),[e,r,i/255*1e3/10]},l.rgb.hwb=function(t){var e=t[0],r=t[1],a=t[2];return[l.rgb.hsl(t)[0],1/255*Math.min(e,Math.min(r,a))*100,100*(a=1-1/255*Math.max(e,Math.max(r,a)))]},l.rgb.cmyk=function(t){var e,r=t[0]/255,a=t[1]/255,n=t[2]/255;return[100*((1-r-(e=Math.min(1-r,1-a,1-n)))/(1-e)||0),100*((1-a-e)/(1-e)||0),100*((1-n-e)/(1-e)||0),100*e]},l.rgb.keyword=function(t){var e=n[t];if(e)return e;var r,o,l,i=1/0;for(var s in a)if(a.hasOwnProperty(s)){var u=(o=t,l=a[s],Math.pow(o[0]-l[0],2)+Math.pow(o[1]-l[1],2)+Math.pow(o[2]-l[2],2));u.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(r=r>.04045?Math.pow((r+.055)/1.055,2.4):r/12.92)+.1805*(a=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92)),100*(.2126*e+.7152*r+.0722*a),100*(.0193*e+.1192*r+.9505*a)]},l.rgb.lab=function(t){var e=l.rgb.xyz(t),r=e[0],a=e[1],n=e[2];return a/=100,n/=108.883,r=(r/=95.047)>.008856?Math.pow(r,1/3):7.787*r+16/116,[116*(a=a>.008856?Math.pow(a,1/3):7.787*a+16/116)-16,500*(r-a),200*(a-(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116))]},l.hsl.rgb=function(t){var e,r,a,n,o,l=t[0]/360,i=t[1]/100,s=t[2]/100;if(0===i)return[o=255*s,o,o];e=2*s-(r=s<.5?s*(1+i):s+i-s*i),n=[0,0,0];for(var u=0;u<3;u++)(a=l+1/3*-(u-1))<0&&a++,a>1&&a--,o=6*a<1?e+6*(r-e)*a:2*a<1?r:3*a<2?e+(r-e)*(2/3-a)*6:e,n[u]=255*o;return n},l.hsl.hsv=function(t){var e=t[0],r=t[1]/100,a=t[2]/100,n=r,o=Math.max(a,.01);return r*=(a*=2)<=1?a:2-a,n*=o<=1?o:2-o,[e,100*(0===a?2*n/(o+n):2*r/(a+r)),(a+r)/2*100]},l.hsv.rgb=function(t){var e=t[0]/60,r=t[1]/100,a=t[2]/100,n=Math.floor(e)%6,o=e-Math.floor(e),l=255*a*(1-r),i=255*a*(1-r*o),s=255*a*(1-r*(1-o));switch(a*=255,n){case 0:return[a,s,l];case 1:return[i,a,l];case 2:return[l,a,s];case 3:return[l,i,a];case 4:return[s,l,a];case 5:return[a,l,i]}},l.hsv.hsl=function(t){var e,r,a,n=t[0],o=t[1]/100,l=t[2]/100,i=Math.max(l,.01);return a=(2-o)*l,r=o*i,[n,100*(r=(r/=(e=(2-o)*i)<=1?e:2-e)||0),100*(a/=2)]},l.hwb.rgb=function(t){var e,r,a,n,o,l,i,s=t[0]/360,u=t[1]/100,c=t[2]/100,h=u+c;switch(h>1&&(u/=h,c/=h),r=1-c,a=6*s-(e=Math.floor(6*s)),0!=(1&e)&&(a=1-a),n=u+a*(r-u),e){default:case 6:case 0:o=r,l=n,i=u;break;case 1:o=n,l=r,i=u;break;case 2:o=u,l=r,i=n;break;case 3:o=u,l=n,i=r;break;case 4:o=n,l=u,i=r;break;case 5:o=r,l=u,i=n}return[255*o,255*l,255*i]},l.cmyk.rgb=function(t){var e=t[0]/100,r=t[1]/100,a=t[2]/100,n=t[3]/100;return[255*(1-Math.min(1,e*(1-n)+n)),255*(1-Math.min(1,r*(1-n)+n)),255*(1-Math.min(1,a*(1-n)+n))]},l.xyz.rgb=function(t){var e,r,a,n=t[0]/100,o=t[1]/100,l=t[2]/100;return r=-.9689*n+1.8758*o+.0415*l,a=.0557*n+-.204*o+1.057*l,e=(e=3.2406*n+-1.5372*o+-.4986*l)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,r=r>.0031308?1.055*Math.pow(r,1/2.4)-.055:12.92*r,a=a>.0031308?1.055*Math.pow(a,1/2.4)-.055:12.92*a,[255*(e=Math.min(Math.max(0,e),1)),255*(r=Math.min(Math.max(0,r),1)),255*(a=Math.min(Math.max(0,a),1))]},l.xyz.lab=function(t){var e=t[0],r=t[1],a=t[2];return r/=100,a/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116)-16,500*(e-r),200*(r-(a=a>.008856?Math.pow(a,1/3):7.787*a+16/116))]},l.lab.xyz=function(t){var e,r,a,n=t[0],o=t[1],l=t[2];e=o/500+(r=(n+16)/116),a=r-l/200;var i=Math.pow(r,3),s=Math.pow(e,3),u=Math.pow(a,3);return r=i>.008856?i:(r-16/116)/7.787,e=s>.008856?s:(e-16/116)/7.787,a=u>.008856?u:(a-16/116)/7.787,[e*=95.047,r*=100,a*=108.883]},l.lab.lch=function(t){var e,r=t[0],a=t[1],n=t[2];return(e=360*Math.atan2(n,a)/2/Math.PI)<0&&(e+=360),[r,Math.sqrt(a*a+n*n),e]},l.lch.lab=function(t){var e,r=t[0],a=t[1];return e=t[2]/360*2*Math.PI,[r,a*Math.cos(e),a*Math.sin(e)]},l.rgb.ansi16=function(t){var e=t[0],r=t[1],a=t[2],n=1 in arguments?arguments[1]:l.rgb.hsv(t)[2];if(0===(n=Math.round(n/50)))return 30;var o=30+(Math.round(a/255)<<2|Math.round(r/255)<<1|Math.round(e/255));return 2===n&&(o+=60),o},l.hsv.ansi16=function(t){return l.rgb.ansi16(l.hsv.rgb(t),t[2])},l.rgb.ansi256=function(t){var e=t[0],r=t[1],a=t[2];return e===r&&r===a?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(a/255*5)},l.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var r=.5*(1+~~(t>50));return[(1&e)*r*255,(e>>1&1)*r*255,(e>>2&1)*r*255]},l.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var r;return t-=16,[Math.floor(t/36)/5*255,Math.floor((r=t%36)/6)/5*255,r%6/5*255]},l.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},l.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var r=e[0];3===e[0].length&&(r=r.split("").map(function(t){return t+t}).join(""));var a=parseInt(r,16);return[a>>16&255,a>>8&255,255&a]},l.rgb.hcg=function(t){var e,r=t[0]/255,a=t[1]/255,n=t[2]/255,o=Math.max(Math.max(r,a),n),l=Math.min(Math.min(r,a),n),i=o-l;return e=i<=0?0:o===r?(a-n)/i%6:o===a?2+(n-r)/i:4+(r-a)/i+4,e/=6,[360*(e%=1),100*i,100*(i<1?l/(1-i):0)]},l.hsl.hcg=function(t){var e,r=t[1]/100,a=t[2]/100,n=0;return(e=a<.5?2*r*a:2*r*(1-a))<1&&(n=(a-.5*e)/(1-e)),[t[0],100*e,100*n]},l.hsv.hcg=function(t){var e=t[1]/100,r=t[2]/100,a=e*r,n=0;return a<1&&(n=(r-a)/(1-a)),[t[0],100*a,100*n]},l.hcg.rgb=function(t){var e=t[0]/360,r=t[1]/100,a=t[2]/100;if(0===r)return[255*a,255*a,255*a];var n,o=[0,0,0],l=e%1*6,i=l%1,s=1-i;switch(Math.floor(l)){case 0:o[0]=1,o[1]=i,o[2]=0;break;case 1:o[0]=s,o[1]=1,o[2]=0;break;case 2:o[0]=0,o[1]=1,o[2]=i;break;case 3:o[0]=0,o[1]=s,o[2]=1;break;case 4:o[0]=i,o[1]=0,o[2]=1;break;default:o[0]=1,o[1]=0,o[2]=s}return n=(1-r)*a,[255*(r*o[0]+n),255*(r*o[1]+n),255*(r*o[2]+n)]},l.hcg.hsv=function(t){var e=t[1]/100,r=e+t[2]/100*(1-e),a=0;return r>0&&(a=e/r),[t[0],100*a,100*r]},l.hcg.hsl=function(t){var e=t[1]/100,r=t[2]/100*(1-e)+.5*e,a=0;return r>0&&r<.5?a=e/(2*r):r>=.5&&r<1&&(a=e/(2*(1-r))),[t[0],100*a,100*r]},l.hcg.hwb=function(t){var e=t[1]/100,r=e+t[2]/100*(1-e);return[t[0],100*(r-e),100*(1-r)]},l.hwb.hcg=function(t){var e=t[1]/100,r=1-t[2]/100,a=r-e,n=0;return a<1&&(n=(r-a)/(1-a)),[t[0],100*a,100*n]},l.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},l.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},l.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},l.gray.hsl=l.gray.hsv=function(t){return[0,0,t[0]]},l.gray.hwb=function(t){return[0,100,t[0]]},l.gray.cmyk=function(t){return[0,0,0,t[0]]},l.gray.lab=function(t){return[t[0],0,0]},l.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),r=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(r.length)+r},l.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}},9:function(t,e,r){"use strict";t.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/color/Readonly.js b/public/extensions/core/interfaces/color/Readonly.js new file mode 100644 index 0000000000..90b4d5f56f --- /dev/null +++ b/public/extensions/core/interfaces/color/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(r){var t={};function e(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return r[n].call(a.exports,a,a.exports,e),a.l=!0,a.exports}return e.m=r,e.c=t,e.d=function(r,t,n){e.o(r,t)||Object.defineProperty(r,t,{configurable:!1,enumerable:!0,get:n})},e.r=function(r){Object.defineProperty(r,"__esModule",{value:!0})},e.n=function(r){var t=r&&r.__esModule?function(){return r.default}:function(){return r};return e.d(t,"a",t),t},e.o=function(r,t){return Object.prototype.hasOwnProperty.call(r,t)},e.p="",e(e.s=71)}({0:function(r,t,e){"use strict";function n(r,t,e,n,a,o,i,s){var l=typeof(r=r||{}).default;"object"!==l&&"function"!==l||(r=r.default);var u,h="function"==typeof r?r.options:r;if(t&&(h.render=t,h.staticRenderFns=e,h._compiled=!0),n&&(h.functional=!0),o&&(h._scopeId=o),i?(u=function(r){(r=r||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(r=__VUE_SSR_CONTEXT__),a&&a.call(this,r),r&&r._registeredComponents&&r._registeredComponents.add(i)},h._ssrRegister=u):a&&(u=s?function(){a.call(this,this.$root.$options.shadowRoot)}:a),u)if(h.functional){h._injectStyles=u;var c=h.render;h.render=function(r,t){return u.call(t),c(r,t)}}else{var f=h.beforeCreate;h.beforeCreate=f?[].concat(f,u):[u]}return{exports:r,options:h}}e.d(t,"a",function(){return n})},1:function(r,t){r.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},15:function(r,t,e){var n=e(8);function a(r){var t=function(){for(var r={},t=Object.keys(n),e=t.length,a=0;a1&&(t=Array.prototype.slice.call(arguments));var e=r(t);if("object"===(void 0===e?"undefined":n(e)))for(var a=e.length,o=0;o1&&(t=Array.prototype.slice.call(arguments)),r(t))};return"conversion"in r&&(t.conversion=r.conversion),t}(a)})}),r.exports=i},17:function(r,t,e){"use strict";r.exports=function(r){return!(!r||"string"==typeof r)&&(r instanceof Array||Array.isArray(r)||r.length>=0&&(r.splice instanceof Function||Object.getOwnPropertyDescriptor(r,r.length-1)&&"String"!==r.constructor.name))}},18:function(r,t,e){"use strict";var n=e(17),a=Array.prototype.concat,o=Array.prototype.slice,i=r.exports=function(r){for(var t=[],e=0,i=r.length;e=4&&1!==r[3]&&(t=", "+r[3]),"hwb("+r[0]+", "+r[1]+"%, "+r[2]+"%"+t+")"},s.to.keyword=function(r){return o[r.slice(0,3)]}},2:function(r,t,e){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(r,t){for(var e=[],n={},a=0;ae.parts.length&&(n.parts.length=e.parts.length)}else{var o=[];for(a=0;a>16&255,r>>8&255,255&r],this.valpha=1;else{this.valpha=1;var d=Object.keys(r);"alpha"in r&&(d.splice(d.indexOf("alpha"),1),this.valpha="number"==typeof r.alpha?r.alpha:0);var v=d.sort().join("");if(!(v in s))throw new Error("Unable to parse color from object: "+JSON.stringify(r));this.model=s[v];var g=a[this.model].labels,b=[];for(e=0;ee?(t+.05)/(e+.05):(e+.05)/(t+.05)},level:function(r){var t=this.contrast(r);return t>=7.1?"AAA":t>=4.5?"AA":""},isDark:function(){var r=this.rgb().color;return(299*r[0]+587*r[1]+114*r[2])/1e3<128},isLight:function(){return!this.isDark()},negate:function(){for(var r=this.rgb(),t=0;t<3;t++)r.color[t]=255-r.color[t];return r},lighten:function(r){var t=this.hsl();return t.color[2]+=t.color[2]*r,t},darken:function(r){var t=this.hsl();return t.color[2]-=t.color[2]*r,t},saturate:function(r){var t=this.hsl();return t.color[1]+=t.color[1]*r,t},desaturate:function(r){var t=this.hsl();return t.color[1]-=t.color[1]*r,t},whiten:function(r){var t=this.hwb();return t.color[1]+=t.color[1]*r,t},blacken:function(r){var t=this.hwb();return t.color[2]+=t.color[2]*r,t},grayscale:function(){var r=this.rgb().color,t=.3*r[0]+.59*r[1]+.11*r[2];return u.rgb(t,t,t)},fade:function(r){return this.alpha(this.valpha-this.valpha*r)},opaquer:function(r){return this.alpha(this.valpha+this.valpha*r)},rotate:function(r){var t=this.hsl(),e=t.color[0];return e=(e=(e+r)%360)<0?360+e:e,t.color[0]=e,t},mix:function(r,t){var e=r.rgb(),n=this.rgb(),a=void 0===t?.5:t,o=2*a-1,i=e.alpha()-n.alpha(),s=((o*i==-1?o:(o+i)/(1+o*i))+1)/2,l=1-s;return u.rgb(s*e.red()+l*n.red(),s*e.green()+l*n.green(),s*e.blue()+l*n.blue(),e.alpha()*a+n.alpha()*(1-a))}},Object.keys(a).forEach(function(r){if(-1===i.indexOf(r)){var t=a[r].channels;u.prototype[r]=function(){if(this.model===r)return new u(this);if(arguments.length)return new u(arguments,r);var e,n="number"==typeof arguments[t]?t:this.valpha;return new u((e=a[this.model][r].raw(this.color),Array.isArray(e)?e:[e]).concat(n),r)},u[r]=function(e){return"number"==typeof e&&(e=f(o.call(arguments),t)),new u(e,r)}}}),r.exports=u},71:function(r,t,e){"use strict";e.r(t),e(153);var n=e(1),a=e.n(n),o=e(6),i=e.n(o),s={mixins:[a.a],computed:{displayValue:function(){if("hex"===this.options.output?this.value:Array.isArray(this.value)?this.value:this.value.split(","),!1===this.options.formatValue)return!1===Boolean(this.value)?"":"hex"===this.options.output?this.value:this.value.join(", ");if("hex"===this.options.output)return i()(this.value).rgb().string();try{return i.a[this.options.output](this.value).rgb().string()}catch(r){return null}}}},l=e(0),u=Object(l.a)(s,function(){var r=this.$createElement,t=this._self._c||r;return this.options.formatValue?t("div",{staticClass:"swatch",style:"background-color: "+this.displayValue}):t("div",[this._v(this._s(this.displayValue))])},[],!1,function(r){e(155)},"data-v-fc7e5ea8",null);t.default=u.exports},8:function(r,t,e){var n=e(9),a={};for(var o in n)n.hasOwnProperty(o)&&(a[n[o]]=o);var i=r.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var s in i)if(i.hasOwnProperty(s)){if(!("channels"in i[s]))throw new Error("missing channels property: "+s);if(!("labels"in i[s]))throw new Error("missing channel labels property: "+s);if(i[s].labels.length!==i[s].channels)throw new Error("channel and label counts mismatch: "+s);var l=i[s].channels,u=i[s].labels;delete i[s].channels,delete i[s].labels,Object.defineProperty(i[s],"channels",{value:l}),Object.defineProperty(i[s],"labels",{value:u})}i.rgb.hsl=function(r){var t,e,n=r[0]/255,a=r[1]/255,o=r[2]/255,i=Math.min(n,a,o),s=Math.max(n,a,o),l=s-i;return s===i?t=0:n===s?t=(a-o)/l:a===s?t=2+(o-n)/l:o===s&&(t=4+(n-a)/l),(t=Math.min(60*t,360))<0&&(t+=360),e=(i+s)/2,[t,100*(s===i?0:e<=.5?l/(s+i):l/(2-s-i)),100*e]},i.rgb.hsv=function(r){var t,e,n=r[0],a=r[1],o=r[2],i=Math.min(n,a,o),s=Math.max(n,a,o),l=s-i;return e=0===s?0:l/s*1e3/10,s===i?t=0:n===s?t=(a-o)/l:a===s?t=2+(o-n)/l:o===s&&(t=4+(n-a)/l),(t=Math.min(60*t,360))<0&&(t+=360),[t,e,s/255*1e3/10]},i.rgb.hwb=function(r){var t=r[0],e=r[1],n=r[2];return[i.rgb.hsl(r)[0],1/255*Math.min(t,Math.min(e,n))*100,100*(n=1-1/255*Math.max(t,Math.max(e,n)))]},i.rgb.cmyk=function(r){var t,e=r[0]/255,n=r[1]/255,a=r[2]/255;return[100*((1-e-(t=Math.min(1-e,1-n,1-a)))/(1-t)||0),100*((1-n-t)/(1-t)||0),100*((1-a-t)/(1-t)||0),100*t]},i.rgb.keyword=function(r){var t=a[r];if(t)return t;var e,o,i,s=1/0;for(var l in n)if(n.hasOwnProperty(l)){var u=(o=r,i=n[l],Math.pow(o[0]-i[0],2)+Math.pow(o[1]-i[1],2)+Math.pow(o[2]-i[2],2));u.04045?Math.pow((t+.055)/1.055,2.4):t/12.92)+.3576*(e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.1805*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)),100*(.2126*t+.7152*e+.0722*n),100*(.0193*t+.1192*e+.9505*n)]},i.rgb.lab=function(r){var t=i.rgb.xyz(r),e=t[0],n=t[1],a=t[2];return n/=100,a/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(a=a>.008856?Math.pow(a,1/3):7.787*a+16/116))]},i.hsl.rgb=function(r){var t,e,n,a,o,i=r[0]/360,s=r[1]/100,l=r[2]/100;if(0===s)return[o=255*l,o,o];t=2*l-(e=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(n=i+1/3*-(u-1))<0&&n++,n>1&&n--,o=6*n<1?t+6*(e-t)*n:2*n<1?e:3*n<2?t+(e-t)*(2/3-n)*6:t,a[u]=255*o;return a},i.hsl.hsv=function(r){var t=r[0],e=r[1]/100,n=r[2]/100,a=e,o=Math.max(n,.01);return e*=(n*=2)<=1?n:2-n,a*=o<=1?o:2-o,[t,100*(0===n?2*a/(o+a):2*e/(n+e)),(n+e)/2*100]},i.hsv.rgb=function(r){var t=r[0]/60,e=r[1]/100,n=r[2]/100,a=Math.floor(t)%6,o=t-Math.floor(t),i=255*n*(1-e),s=255*n*(1-e*o),l=255*n*(1-e*(1-o));switch(n*=255,a){case 0:return[n,l,i];case 1:return[s,n,i];case 2:return[i,n,l];case 3:return[i,s,n];case 4:return[l,i,n];case 5:return[n,i,s]}},i.hsv.hsl=function(r){var t,e,n,a=r[0],o=r[1]/100,i=r[2]/100,s=Math.max(i,.01);return n=(2-o)*i,e=o*s,[a,100*(e=(e/=(t=(2-o)*s)<=1?t:2-t)||0),100*(n/=2)]},i.hwb.rgb=function(r){var t,e,n,a,o,i,s,l=r[0]/360,u=r[1]/100,h=r[2]/100,c=u+h;switch(c>1&&(u/=c,h/=c),e=1-h,n=6*l-(t=Math.floor(6*l)),0!=(1&t)&&(n=1-n),a=u+n*(e-u),t){default:case 6:case 0:o=e,i=a,s=u;break;case 1:o=a,i=e,s=u;break;case 2:o=u,i=e,s=a;break;case 3:o=u,i=a,s=e;break;case 4:o=a,i=u,s=e;break;case 5:o=e,i=u,s=a}return[255*o,255*i,255*s]},i.cmyk.rgb=function(r){var t=r[0]/100,e=r[1]/100,n=r[2]/100,a=r[3]/100;return[255*(1-Math.min(1,t*(1-a)+a)),255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a))]},i.xyz.rgb=function(r){var t,e,n,a=r[0]/100,o=r[1]/100,i=r[2]/100;return e=-.9689*a+1.8758*o+.0415*i,n=.0557*a+-.204*o+1.057*i,t=(t=3.2406*a+-1.5372*o+-.4986*i)>.0031308?1.055*Math.pow(t,1/2.4)-.055:12.92*t,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,[255*(t=Math.min(Math.max(0,t),1)),255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1))]},i.xyz.lab=function(r){var t=r[0],e=r[1],n=r[2];return e/=100,n/=108.883,t=(t/=95.047)>.008856?Math.pow(t,1/3):7.787*t+16/116,[116*(e=e>.008856?Math.pow(e,1/3):7.787*e+16/116)-16,500*(t-e),200*(e-(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116))]},i.lab.xyz=function(r){var t,e,n,a=r[0],o=r[1],i=r[2];t=o/500+(e=(a+16)/116),n=e-i/200;var s=Math.pow(e,3),l=Math.pow(t,3),u=Math.pow(n,3);return e=s>.008856?s:(e-16/116)/7.787,t=l>.008856?l:(t-16/116)/7.787,n=u>.008856?u:(n-16/116)/7.787,[t*=95.047,e*=100,n*=108.883]},i.lab.lch=function(r){var t,e=r[0],n=r[1],a=r[2];return(t=360*Math.atan2(a,n)/2/Math.PI)<0&&(t+=360),[e,Math.sqrt(n*n+a*a),t]},i.lch.lab=function(r){var t,e=r[0],n=r[1];return t=r[2]/360*2*Math.PI,[e,n*Math.cos(t),n*Math.sin(t)]},i.rgb.ansi16=function(r){var t=r[0],e=r[1],n=r[2],a=1 in arguments?arguments[1]:i.rgb.hsv(r)[2];if(0===(a=Math.round(a/50)))return 30;var o=30+(Math.round(n/255)<<2|Math.round(e/255)<<1|Math.round(t/255));return 2===a&&(o+=60),o},i.hsv.ansi16=function(r){return i.rgb.ansi16(i.hsv.rgb(r),r[2])},i.rgb.ansi256=function(r){var t=r[0],e=r[1],n=r[2];return t===e&&e===n?t<8?16:t>248?231:Math.round((t-8)/247*24)+232:16+36*Math.round(t/255*5)+6*Math.round(e/255*5)+Math.round(n/255*5)},i.ansi16.rgb=function(r){var t=r%10;if(0===t||7===t)return r>50&&(t+=3.5),[t=t/10.5*255,t,t];var e=.5*(1+~~(r>50));return[(1&t)*e*255,(t>>1&1)*e*255,(t>>2&1)*e*255]},i.ansi256.rgb=function(r){if(r>=232){var t=10*(r-232)+8;return[t,t,t]}var e;return r-=16,[Math.floor(r/36)/5*255,Math.floor((e=r%36)/6)/5*255,e%6/5*255]},i.rgb.hex=function(r){var t=(((255&Math.round(r[0]))<<16)+((255&Math.round(r[1]))<<8)+(255&Math.round(r[2]))).toString(16).toUpperCase();return"000000".substring(t.length)+t},i.hex.rgb=function(r){var t=r.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!t)return[0,0,0];var e=t[0];3===t[0].length&&(e=e.split("").map(function(r){return r+r}).join(""));var n=parseInt(e,16);return[n>>16&255,n>>8&255,255&n]},i.rgb.hcg=function(r){var t,e=r[0]/255,n=r[1]/255,a=r[2]/255,o=Math.max(Math.max(e,n),a),i=Math.min(Math.min(e,n),a),s=o-i;return t=s<=0?0:o===e?(n-a)/s%6:o===n?2+(a-e)/s:4+(e-n)/s+4,t/=6,[360*(t%=1),100*s,100*(s<1?i/(1-s):0)]},i.hsl.hcg=function(r){var t,e=r[1]/100,n=r[2]/100,a=0;return(t=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*t)/(1-t)),[r[0],100*t,100*a]},i.hsv.hcg=function(r){var t=r[1]/100,e=r[2]/100,n=t*e,a=0;return n<1&&(a=(e-n)/(1-n)),[r[0],100*n,100*a]},i.hcg.rgb=function(r){var t=r[0]/360,e=r[1]/100,n=r[2]/100;if(0===e)return[255*n,255*n,255*n];var a,o=[0,0,0],i=t%1*6,s=i%1,l=1-s;switch(Math.floor(i)){case 0:o[0]=1,o[1]=s,o[2]=0;break;case 1:o[0]=l,o[1]=1,o[2]=0;break;case 2:o[0]=0,o[1]=1,o[2]=s;break;case 3:o[0]=0,o[1]=l,o[2]=1;break;case 4:o[0]=s,o[1]=0,o[2]=1;break;default:o[0]=1,o[1]=0,o[2]=l}return a=(1-e)*n,[255*(e*o[0]+a),255*(e*o[1]+a),255*(e*o[2]+a)]},i.hcg.hsv=function(r){var t=r[1]/100,e=t+r[2]/100*(1-t),n=0;return e>0&&(n=t/e),[r[0],100*n,100*e]},i.hcg.hsl=function(r){var t=r[1]/100,e=r[2]/100*(1-t)+.5*t,n=0;return e>0&&e<.5?n=t/(2*e):e>=.5&&e<1&&(n=t/(2*(1-e))),[r[0],100*n,100*e]},i.hcg.hwb=function(r){var t=r[1]/100,e=t+r[2]/100*(1-t);return[r[0],100*(e-t),100*(1-e)]},i.hwb.hcg=function(r){var t=r[1]/100,e=1-r[2]/100,n=e-t,a=0;return n<1&&(a=(e-n)/(1-n)),[r[0],100*n,100*a]},i.apple.rgb=function(r){return[r[0]/65535*255,r[1]/65535*255,r[2]/65535*255]},i.rgb.apple=function(r){return[r[0]/255*65535,r[1]/255*65535,r[2]/255*65535]},i.gray.rgb=function(r){return[r[0]/100*255,r[0]/100*255,r[0]/100*255]},i.gray.hsl=i.gray.hsv=function(r){return[0,0,r[0]]},i.gray.hwb=function(r){return[0,100,r[0]]},i.gray.cmyk=function(r){return[0,0,0,r[0]]},i.gray.lab=function(r){return[r[0],0,0]},i.gray.hex=function(r){var t=255&Math.round(r[0]/100*255),e=((t<<16)+(t<<8)+t).toString(16).toUpperCase();return"000000".substring(e.length)+e},i.rgb.gray=function(r){return[(r[0]+r[1]+r[2])/3/255*100]}},9:function(r,t,e){"use strict";r.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/color/meta.json b/public/extensions/core/interfaces/color/meta.json new file mode 100644 index 0000000000..6d46d02b91 --- /dev/null +++ b/public/extensions/core/interfaces/color/meta.json @@ -0,0 +1,85 @@ +{ + "name": "$t:color", + "version": "1.0.0", + "datatypes": { + "VARCHAR": 30, + "CHAR": 30 + }, + "options": { + "input": { + "name": "$t:input", + "comment": "$t:input_comment", + "interface": "dropdown", + "default": "hex", + "options": { + "choices": { + "hex": "Hex", + "rgb": "RGB", + "hsl": "HSL", + "cmyk": "CMYK" + } + } + }, + "output": { + "name": "$t:output", + "comment": "$t:output_comment", + "interface": "dropdown", + "default": "hex", + "options": { + "choices": { + "hex": "Hex", + "rgb": "RGB", + "hsl": "HSL", + "cmyk": "CMYK" + } + } + }, + "formatValue": { + "name": "$t:format", + "comment": "$t:format_comment", + "interface": "toggle", + "default": true + }, + "palette": { + "name": "$t:palette", + "comment": "$t:palette_comment", + "interface": "tags", + "type": "CSV", + "options": { + "wrapWithDelimiter": false + }, + "default": [ + "#f44336", "#9C27B0", "#039BE5", "#4CAF50", "#FFC107", "#212121" + ] + }, + "paletteOnly": { + "name": "$t:palette_only", + "comment": "$t:palette_only_comment", + "interface": "toggle", + "default": false + }, + "allowAlpha": { + "name": "$t:allow_alpha", + "comment": "$t:allow_alpha_comment", + "interface": "toggle", + "default": false + } + }, + "translation": { + "en-US": { + "color": "Color", + "input": "Input", + "input_comment": "The unit in which the user will enter the data", + "output": "Output", + "output_comment": "The unit in which the data gets saved to the DB", + "format": "Format", + "format_comment": "Show value as color swatch", + "palette": "Palette", + "palette_comment": "Add color options as hex values", + "palette_only": "Palette Only", + "palette_only_comment": "Only allow the user to pick from the palette", + "allow_alpha": "Allow alpha", + "allow_alpha_comment": "Allow values with an alpha channel" + } + } +} diff --git a/public/extensions/core/interfaces/date/Interface.js b/public/extensions/core/interfaces/date/Interface.js new file mode 100644 index 0000000000..292b490712 --- /dev/null +++ b/public/extensions/core/interfaces/date/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=68)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var u=typeof(e=e||{}).default;"object"!==u&&"function"!==u||(e=e.default);var c,f="function"==typeof e?e.options:e;if(t&&(f.render=t,f.staticRenderFns=n,f._compiled=!0),r&&(f.functional=!0),i&&(f._scopeId=i),a?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},f._ssrRegister=c):o&&(c=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(f.functional){f._injectStyles=c;var l=f.render;f.render=function(e,t){return c.call(t),l(e,t)}}else{var d=f.beforeCreate;f.beforeCreate=d?[].concat(d,c):[c]}return{exports:e,options:f}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},151:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".interface-date[data-v-377f2174]{max-width:var(--width-small)}",""])},152:function(e,t,n){var r=n(151);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("ba24de10",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(o=0;oe.length&&(e=t)});var t=e.length;return t<=7?"x-small":t>7&&t<=25?"small":"medium"}},methods:{updateValue:function(e){var t=Array.from(e).filter(function(e){return e.selected&&Boolean(e.value)}).map(function(e){return e.value}).join();t&&this.options.wrapWithDelimiter&&(t=","+t+","),this.$emit("input",t)}}},a=n(0),i=Object(a.a)(o,function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("select",{staticClass:"select",class:e.width,attrs:{disabled:e.readonly,id:e.name,multiple:""},on:{change:function(t){e.updateValue(t.target.options)}}},[e.options.placeholder?n("option",{attrs:{value:"",disabled:e.required}},[e._v(e._s(e.options.placeholder))]):e._e(),e._v(" "),e._l(e.options.choices,function(t,r){return n("option",{domProps:{value:r,selected:e.value&&e.value.includes(r)}},[e._v(e._s(t))])})],2)},[],!1,function(e){n(148)},"data-v-082c0ae7",null);t.default=i.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/dropdown-multiselect/Readonly.js b/public/extensions/core/interfaces/dropdown-multiselect/Readonly.js new file mode 100644 index 0000000000..084d36d971 --- /dev/null +++ b/public/extensions/core/interfaces/dropdown-multiselect/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=53)}({0:function(t,e,n){"use strict";function r(t,e,n,r,o,i,s,u){var a=typeof(t=t||{}).default;"object"!==a&&"function"!==a||(t=t.default);var l,c="function"==typeof t?t.options:t;if(e&&(c.render=e,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),s?(l=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(s)},c._ssrRegister=l):o&&(l=u?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(c.functional){c._injectStyles=l;var p=c.render;c.render=function(t,e){return l.call(e),p(t,e)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,l):[l]}return{exports:t,options:c}}n.d(e,"a",function(){return r})},1:function(t,e){t.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},53:function(t,e,n){"use strict";n.r(e);var r=n(1),o={mixins:[n.n(r).a],computed:{displayValue:function(){var t=this,e=this.value;if(e){var n="string"==typeof this.options.choices?JSON.parse(this.options.choices):this.options.choices;this.options.wrapWithDelimiter&&(e=e.slice(1,-1)),e=(e=e.split(",")).map(function(e){return"text"===t.options.formatting?n[e]:e}).join(", ")}return e}}},i=n(0),s=Object(i.a)(o,function(){var t=this.$createElement;return(this._self._c||t)("span",[this._v(this._s(this.displayValue))])},[],!1,null,null,null);e.default=s.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/dropdown-multiselect/meta.json b/public/extensions/core/interfaces/dropdown-multiselect/meta.json new file mode 100644 index 0000000000..c3360dc9ba --- /dev/null +++ b/public/extensions/core/interfaces/dropdown-multiselect/meta.json @@ -0,0 +1,56 @@ +{ + "name": "$t:dropdown_multiselect", + "version": "1.0.0", + "datatypes": { + "VARCHAR": 100, + "CHAR": 50 + }, + "options": { + "choices": { + "name": "$t:choices", + "comment": "$t:choices_comment", + "interface": "json", + "default": { + "option1": "Option 1", + "option2": "Option 2" + } + }, + "placeholder": { + "name": "$t:placeholder", + "comment": "$t:placeholder_comment", + "interface": "text-input", + "length": 200 + }, + "wrapWithDelimiter": { + "name": "$t:wrap", + "comment": "$t:wrap_comment", + "interface": "toggle", + "default": true + }, + "formatting": { + "name": "$t:format", + "comment": "$t:format_comment", + "interface": "radio-buttons", + "default": "text", + "options": { + "choices": { + "text": "Display Text", + "value": "Value" + } + } + } + }, + "translation": { + "en-US": { + "dropdown_multiselect": "Dropdown Multiselect", + "choices": "Choises", + "choices_comment": "Enter JSON key value pairs with the saved value and text displayed", + "placeholder": "Placeholder", + "placeholder_comment": "Enter placeholder text", + "wrap": "Wrap with Delimiter", + "wrap_comment": "Wrap the values with a pair of delimiters to allow strict searching for a single value", + "format": "Format", + "format_comment": "The output format on the listings" + } + } +} diff --git a/public/extensions/core/interfaces/dropdown/Interface.js b/public/extensions/core/interfaces/dropdown/Interface.js new file mode 100644 index 0000000000..c7cbd5483d --- /dev/null +++ b/public/extensions/core/interfaces/dropdown/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=50)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var u=typeof(e=e||{}).default;"object"!==u&&"function"!==u||(e=e.default);var c,l="function"==typeof e?e.options:e;if(t&&(l.render=t,l.staticRenderFns=n,l._compiled=!0),r&&(l.functional=!0),i&&(l._scopeId=i),a?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},l._ssrRegister=c):o&&(c=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(l.functional){l._injectStyles=c;var d=l.render;l.render=function(e,t){return c.call(t),d(e,t)}}else{var f=l.beforeCreate;l.beforeCreate=f?[].concat(f,c):[c]}return{exports:e,options:l}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},145:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".x-small[data-v-f70e909c]{max-width:var(--width-x-small)}.small[data-v-f70e909c]{max-width:var(--width-small)}.medium[data-v-f70e909c]{max-width:var(--width-normal)}",""])},146:function(e,t,n){var r=n(145);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("2564741a",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;oe.length&&(e=t)});var t=e.length;return t<=7?"x-small":t>7&&t<=25?"small":"medium"}}},i=n(0),a=Object(i.a)(o,function(){var e=this,t=e.$createElement;return(e._self._c||t)("v-select",{class:e.width,attrs:{value:e.value,disabled:e.readonly,id:e.name,options:e.choices,placeholder:e.options.placeholder},on:{input:function(t){e.$emit("input",t)}}})},[],!1,function(e){n(146)},"data-v-f70e909c",null);t.default=a.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/dropdown/Readonly.js b/public/extensions/core/interfaces/dropdown/Readonly.js new file mode 100644 index 0000000000..af18b9878e --- /dev/null +++ b/public/extensions/core/interfaces/dropdown/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=48)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,s,u){var a=typeof(e=e||{}).default;"object"!==a&&"function"!==a||(e=e.default);var l,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),s?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(s)},c._ssrRegister=l):o&&(l=u?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(c.functional){c._injectStyles=l;var p=c.render;c.render=function(e,t){return l.call(t),p(e,t)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},48:function(e,t,n){"use strict";n.r(t);var r=n(1),o={mixins:[n.n(r).a],computed:{displayValue:function(){return"text"===this.options.formatting?("string"==typeof this.options.choices?JSON.parse(this.options.choices):this.options.choices)[this.value]:this.value}}},i=n(0),s=Object(i.a)(o,function(){var e=this.$createElement;return(this._self._c||e)("span",[this._v(this._s(this.displayValue))])},[],!1,null,null,null);t.default=s.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/dropdown/meta.json b/public/extensions/core/interfaces/dropdown/meta.json new file mode 100644 index 0000000000..385ee1a1a4 --- /dev/null +++ b/public/extensions/core/interfaces/dropdown/meta.json @@ -0,0 +1,46 @@ +{ + "name": "$t:dropdown", + "version": "1.0.0", + "datatypes": { + "VARCHAR": 100 + }, + "options": { + "choices": { + "name": "$t:choices", + "comment": "$t:choices_comment", + "interface": "json", + "type": "JSON", + "default": { + "value1": "$t:option 1", + "value2": "$t:option 2" + } + }, + "placeholder": { + "name": "$t:placeholder", + "interface": "text-input", + "comment": "$t:placeholder_comment", + "default": "$t:placeholder_default", + "length": 200 + }, + "formatting": { + "name": "$t:formatting", + "comment": "$t:formatting_comment", + "interface": "toggle", + "type": "BOOLEAN", + "default": true + } + }, + "translation": { + "en-US": { + "dropdown": "Dropdown", + "choices": "Choices", + "choices_comment": "Enter JSON key value pairs with the saved value and text displayed.", + "placeholder": "Placeholder", + "placeholder_comment": "Static text that shown before a value is selected", + "option": "Option", + "formatting": "Show display text", + "formatting_comment": "Render the values as the display values", + "placeholder_default": "Choose an option" + } + } +} diff --git a/public/extensions/core/interfaces/encrypted/Interface.js b/public/extensions/core/interfaces/encrypted/Interface.js new file mode 100644 index 0000000000..b76e9012da --- /dev/null +++ b/public/extensions/core/interfaces/encrypted/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=45)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,u){var s=typeof(e=e||{}).default;"object"!==s&&"function"!==s||(e=e.default);var l,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),a?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},c._ssrRegister=l):o&&(l=u?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(c.functional){c._injectStyles=l;var d=c.render;c.render=function(e,t){return l.call(t),d(e,t)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},143:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".x-small[data-v-240e8b58]{max-width:var(--width-x-small)}.small[data-v-240e8b58]{max-width:var(--width-small)}.medium[data-v-240e8b58]{max-width:var(--width-normal)}",""])},144:function(e,t,n){var r=n(143);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("0c2ff8a9",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o7&&e<=25?"small":"medium":"normal"},valueChanged:function(){return this.value!==this.originalValue},inputType:function(){return this.options.hide?"password":"text"},lockIcon:function(){return this.valueChanged?"lock_open":"lock_outline"},iconColor:function(){return this.valueChanged?"warning":"accent"}}},i=n(0),a=Object(i.a)(o,function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"interface-encrypted"},[n("v-input",{class:e.width,attrs:{type:e.inputType,value:e.value,"icon-right":e.lockIcon,"icon-right-color":e.iconColor},on:{input:function(t){e.$emit("input",t)}}})],1)},[],!1,function(e){n(144)},"data-v-240e8b58",null);t.default=a.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/encrypted/Readonly.js b/public/extensions/core/interfaces/encrypted/Readonly.js new file mode 100644 index 0000000000..0e06aa48b7 --- /dev/null +++ b/public/extensions/core/interfaces/encrypted/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=30)}({0:function(t,e,n){"use strict";function r(t,e,n,r,o,i,s,u){var c=typeof(t=t||{}).default;"object"!==c&&"function"!==c||(t=t.default);var a,l="function"==typeof t?t.options:t;if(e&&(l.render=e,l.staticRenderFns=n,l._compiled=!0),r&&(l.functional=!0),i&&(l._scopeId=i),s?(a=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(s)},l._ssrRegister=a):o&&(a=u?function(){o.call(this,this.$root.$options.shadowRoot)}:o),a)if(l.functional){l._injectStyles=a;var f=l.render;l.render=function(t,e){return a.call(e),f(t,e)}}else{var _=l.beforeCreate;l.beforeCreate=_?[].concat(_,a):[a]}return{exports:t,options:l}}n.d(e,"a",function(){return r})},30:function(t,e,n){"use strict";n.r(e);var r=n(0),o=Object(r.a)(null,function(){var t=this.$createElement;return(this._self._c||t)("i",{staticClass:"material-icons"},[this._v("lock")])},[],!1,null,null,null);e.default=o.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/encrypted/meta.json b/public/extensions/core/interfaces/encrypted/meta.json new file mode 100644 index 0000000000..d25d68d7f2 --- /dev/null +++ b/public/extensions/core/interfaces/encrypted/meta.json @@ -0,0 +1,83 @@ +{ + "name": "$t:encrypted", + "version": "1.0.0", + "datatypes": { + "VARCHAR": 100, + "CHAR": 10, + "TINYTEXT": null, + "TEXT": null, + "MEDIUMTEXT": null, + "LONGTEXT": null + }, + "options": { + "hide": { + "name": "$t:hide", + "comment": "$t:hide_comment", + "interface": "toggle", + "default": false + }, + "placeholder": { + "name": "$t:placeholder", + "comment": "$t:placeholder_comment", + "interface": "text-input", + "length": 100 + }, + "showHash": { + "name": "$t:show_hash", + "comment": "$t:show_hash_comment", + "interface": "toggle", + "default": false + }, + "hashingType": { + "name": "$t:hashing_type", + "comment": "$t:hashing_type_comment", + "interface": "dropdown", + "default": "core", + "options": { + "choices": { + "core": "Default", + "bcrypt": "bcrypt", + "md5": "md5", + "sha1": "SHA-1", + "sha224": "SHA-224", + "sha256": "SHA-256", + "sha384": "SHA-384", + "sha512": "SHA-512" + } + } + }, + "width": { + "name": "$t:width", + "comment": "$t:width_comment", + "interface": "dropdown", + "default": "auto", + "options": { + "choices": { + "auto": "$t:auto", + "small": "$t:small", + "medium": "$t:medium", + "large": "$t:large" + } + } + } + }, + "translation": { + "en-US": { + "encrypted": "Encrypted", + "hide": "Hide Value", + "hide_comment": "Display dots instead of the characters you enter", + "placeholder": "Placeholder", + "placeholder_comment": "Enter placeholder text", + "show_hash": "Show the hashed output", + "show_hash_comment": "Display the saved hash", + "hashing_type": "Hashing Type", + "hashing_type_comment": "What method of hashing to use", + "width": "Size", + "width_comment": "Set what width to use for the input", + "auto": "Automatic", + "small": "Small", + "medium": "Medium", + "large": "Large" + } + } +} diff --git a/public/extensions/core/interfaces/file-size/Interface.js b/public/extensions/core/interfaces/file-size/Interface.js new file mode 100644 index 0000000000..b7d314e965 --- /dev/null +++ b/public/extensions/core/interfaces/file-size/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=40)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var u=typeof(e=e||{}).default;"object"!==u&&"function"!==u||(e=e.default);var l,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),a?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},c._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(c.functional){c._injectStyles=l;var f=c.render;c.render=function(e,t){return l.call(t),f(e,t)}}else{var d=c.beforeCreate;c.beforeCreate=d?[].concat(d,l):[l]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},141:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".interface-file-size[data-v-036972ea]{max-width:var(--width-small)}",""])},142:function(e,t,n){var r=n(141);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("7edcd604",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o0&&void 0!==arguments[0]?arguments[0]:0,t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=t?1e3:1024;if(!1===Boolean(e))return"--";if(Math.abs(e)=n&&olegend{position:absolute;overflow:hidden;height:1px;width:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap}.filepond--file{position:static;display:-ms-flexbox;display:flex;height:100%;-ms-flex-align:start;align-items:flex-start;padding:.5625em;color:#fff;border-radius:.5em}.filepond--file .filepond--file-status{margin-left:auto;margin-right:2.25em}.filepond--file .filepond--processing-complete-indicator{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:2}.filepond--file .filepond--file-action-button,.filepond--file .filepond--processing-complete-indicator{position:absolute}.filepond--file .filepond--progress-indicator{position:absolute;top:.75em;right:.75em}.filepond--file .filepond--action-remove-item{left:.5625em}.filepond--file .filepond--file-action-button:not(.filepond--action-remove-item),.filepond--file .filepond--processing-complete-indicator{right:.5625em}[data-filepond-item-state*=error] .filepond--file-info,[data-filepond-item-state*=invalid] .filepond--file-info,[data-filepond-item-state=cancelled] .filepond--file-info{margin-right:2.25em}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing svg{-webkit-animation:fall .5s .125s linear both;animation:fall .5s .125s linear both}[data-filepond-item-state=processing-complete] .filepond--file-info-sub,[data-filepond-item-state=processing-complete] .filepond--file-status-sub{opacity:0}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing~.filepond--file-info .filepond--file-info-sub,[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}[data-filepond-item-state*=error] .filepond--file-wrapper,[data-filepond-item-state*=error] .filepond--panel,[data-filepond-item-state*=invalid] .filepond--file-wrapper,[data-filepond-item-state*=invalid] .filepond--panel{-webkit-animation:shake .65s linear both;animation:shake .65s linear both}[data-filepond-item-state*=busy] .filepond--progress-indicator svg{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes shake{10%,90%{-webkit-transform:translateX(-.0625em);transform:translateX(-.0625em)}20%,80%{-webkit-transform:translateX(.125em);transform:translateX(.125em)}30%,50%,70%{-webkit-transform:translateX(-.25em);transform:translateX(-.25em)}40%,60%{-webkit-transform:translateX(.25em);transform:translateX(.25em)}}@keyframes shake{10%,90%{-webkit-transform:translateX(-.0625em);transform:translateX(-.0625em)}20%,80%{-webkit-transform:translateX(.125em);transform:translateX(.125em)}30%,50%,70%{-webkit-transform:translateX(-.25em);transform:translateX(-.25em)}40%,60%{-webkit-transform:translateX(.25em);transform:translateX(.25em)}}@-webkit-keyframes fall{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}70%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}to{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes fall{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}70%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}to{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.filepond--hopper[data-hopper-state=drag-over]>*{pointer-events:none}.filepond--progress-indicator{z-index:103}.filepond--file-action-button{z-index:102}.filepond--file-status{z-index:101}.filepond--file-info{z-index:100}.filepond--item{position:absolute;top:0;left:0;right:0;padding:0;margin:0 0 .5em;will-change:transform,opacity}.filepond--item>.filepond--panel{z-index:1}.filepond--item>.filepond--panel .filepond--panel-bottom{box-shadow:0 .0625em .125em -.0625em rgba(0,0,0,.25)}.filepond--item>.filepond--file-wrapper{position:relative;z-index:2}.filepond--item-panel{background-color:#64605e}[data-filepond-item-state=processing-complete] .filepond--item-panel{background-color:#369763}[data-filepond-item-state*=error] .filepond--item-panel,[data-filepond-item-state*=invalid] .filepond--item-panel{background-color:#c44e47}.filepond--item-panel{border-radius:.5em;transition:background-color .25s}.filepond--list-scroller{position:absolute;top:0;left:0;right:0;margin:0;will-change:transform}.filepond--list-scroller[data-state=overflow]{overflow-y:scroll;overflow-x:visible;-webkit-overflow-scrolling:touch}.filepond--list-scroller[data-state=overflow] .filepond--list{bottom:0;right:0}.filepond--list-scroller::-webkit-scrollbar{background:transparent}.filepond--list-scroller::-webkit-scrollbar:vertical{width:1em}.filepond--list-scroller::-webkit-scrollbar:horizontal{height:0}.filepond--list-scroller::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.3);border-radius:99999px;border:.3125em solid transparent;background-clip:content-box}.filepond--list{position:absolute;top:0;left:1em;right:1em;margin:0;padding:0;list-style-type:none;will-change:transform}.filepond--panel-root{border-radius:.5em;background-color:#f1f0ef}.filepond--panel{position:absolute;left:0;top:0;right:0;margin:0;height:auto!important;pointer-events:none}.filepond--panel[data-scalable=true]{-webkit-transform-style:preserve-3d;transform-style:preserve-3d;background-color:transparent!important;border:none!important}.filepond--panel[data-scalable=false]{bottom:0}.filepond--panel[data-scalable=false]>div{display:none}.filepond--panel-bottom,.filepond--panel-center,.filepond--panel-top{position:absolute;left:0;top:0;right:0;margin:0;padding:0}.filepond--panel-bottom,.filepond--panel-top{height:.5em}.filepond--panel-top{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important;border-bottom:none!important}.filepond--panel-top:after{content:"";position:absolute;height:2px;left:0;right:0;bottom:-1px;background-color:inherit}.filepond--panel-bottom,.filepond--panel-center{will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:left top;transform-origin:left top;-webkit-transform:translate3d(0,.5em,0);transform:translate3d(0,.5em,0)}.filepond--panel-bottom{border-top-left-radius:0!important;border-top-right-radius:0!important;border-top:none!important}.filepond--panel-bottom:before{content:"";position:absolute;height:2px;left:0;right:0;top:-1px;background-color:inherit}.filepond--panel-center{height:100px!important;border-top:none!important;border-bottom:none!important;border-radius:0!important}.filepond--panel-center:not([style]){visibility:hidden}.filepond--progress-indicator{position:static;width:1.25em;height:1.25em;color:#fff;margin:0;pointer-events:none;will-change:transform,opacity}.filepond--progress-indicator svg{width:100%;height:100%}.filepond--progress-indicator path{fill:none;stroke:currentColor}.filepond--list-scroller{z-index:6}.filepond--drop-label{z-index:5}.filepond--drip{z-index:3}.filepond--root>.filepond--panel{z-index:2}.filepond--browser{z-index:1}.filepond--root{box-sizing:border-box;position:relative;margin-bottom:1em;padding-top:1em;font-size:1rem;line-height:normal;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:450;text-align:left;text-rendering:optimizeLegibility;direction:ltr;contain:layout style size}.filepond--root *{font-size:inherit;box-sizing:inherit;line-height:inherit}',""])},140:function(e,t,n){var r=n(139);"string"==typeof r&&(r=[[e.i,r,""]]);n(5)(r,{hmr:!0,transform:void 0,insertInto:void 0}),r.locals&&(e.exports=r.locals)},20:function(e,t,n){var r,o,i,a,l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};a=function(e){"use strict";var t,n,r="function"==typeof Symbol&&"symbol"===l(Symbol.iterator)?function(e){return void 0===e?"undefined":l(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":void 0===e?"undefined":l(e)},o=(function(){function e(e){var t,n;function r(t,n){try{var i=e[t](n),a=i.value;a instanceof function(e){this.value=e}?Promise.resolve(a.value).then(function(e){r("next",e)},function(e){r("throw",e)}):o(i.done?"return":"normal",i.value)}catch(e){o("throw",e)}}function o(e,o){switch(e){case"return":t.resolve({value:o,done:!0});break;case"throw":t.reject(o);break;default:t.resolve({value:o,done:!1})}(t=t.next)?r(t.key,t.arg):n=null}this._invoke=function(e,o){return new Promise(function(i,a){var l={key:e,arg:o,resolve:i,reject:a,next:null};n?n=n.next=l:(t=n=l,r(e,o))})},"function"!=typeof e.return&&(this.return=void 0)}"function"==typeof Symbol&&Symbol.asyncIterator&&(e.prototype[Symbol.asyncIterator]=function(){return this}),e.prototype.next=function(e){return this._invoke("next",e)},e.prototype.throw=function(e){return this._invoke("throw",e)},e.prototype.return=function(e){return this._invoke("return",e)}}(),Object.assign||function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:null;if(null===n)return e.getAttribute(t)||e.hasAttribute(t);e.setAttribute(t,n)},c=["svg","path"],f=function(e){return c.includes(e)},p=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};"object"===(void 0===t?"undefined":r(t))&&(n=t,t=null);var o=f(e)?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e);return t&&(f(e)?u(o,"class",t):o.className=t),a(n,function(e,t){u(o,e,t)}),o},d=function(e,t,n,r){var i=n[0]||e.left,a=n[1]||e.top,l=i+e.width,s=a+e.height*(r[1]||1),u={element:o({},e),inner:{left:e.left,top:e.top,right:e.right,bottom:e.bottom},outer:{left:i,top:a,right:l,bottom:s}};return t.filter(function(e){return!e.isRectIgnored()}).map(function(e){return e.rect}).forEach(function(e){m(u.inner,o({},e.inner)),m(u.outer,o({},e.outer))}),v(u.inner),u.outer.bottom+=u.element.marginBottom,u.outer.right+=u.element.marginRight,v(u.outer),u},m=function(e,t){t.top+=e.top,t.right+=e.left,t.bottom+=e.top,t.left+=e.left,t.bottom>e.bottom&&(e.bottom=t.bottom),t.right>e.right&&(e.right=t.right)},v=function(e){e.width=e.right-e.left,e.height=e.bottom-e.top},h=function(e){return"number"==typeof e},E=function(e){return e<.5?2*e*e:(4-2*e)*e-1},g={spring:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.stiffness,n=void 0===t?.5:t,r=e.damping,o=void 0===r?.75:r,i=e.mass,a=void 0===i?10:i,l=null,u=null,c=0,f=!1,p=s({interpolate:function(){if(!f){if(!h(l)||!h(u))return f=!0,void(c=0);!function(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:.001;return Math.abs(e-t)0&&void 0!==arguments[0]?arguments[0]:{},t=e.duration,n=void 0===t?500:t,r=e.easing,o=void 0===r?E:r,i=e.delay,a=void 0===i?0:i,l=null,u=void 0,c=void 0,f=!0,p=!1,d=null,m=s({interpolate:function(e){f||null===d||(null===l&&(l=e),e-l=0?o(p?1-c:c):0)*d)):(u=1,f=!0,c=p?0:1,m.onupdate(c*d),m.oncomplete(c*d))))},target:{get:function(){return p?0:d},set:function(e){if(null===d)return d=e,m.onupdate(e),void m.oncomplete(e);e3&&void 0!==arguments[3]&&arguments[3];(t=Array.isArray(t)?t:[t]).forEach(function(t){e.forEach(function(e){var i=e,a=function(){return n[e]},l=function(t){return n[e]=t};"object"===(void 0===e?"undefined":r(e))&&(i=e.key,a=e.getter||a,l=e.setter||l),t[i]&&!o||(t[i]={get:a,set:l})})})},y=function(e){return null==e},T=function(e){return!y(e)},b={opacity:1,scaleX:1,scaleY:1,translateX:0,translateY:0,rotateX:0,rotateY:0,rotateZ:0},R={styles:function(e){var t=e.mixinConfig,n=e.viewProps,r=e.viewInternalAPI,a=e.viewExternalAPI,l=e.view,s=o({},n),u={};I(t,[r,a],n);var c=function(){return l.rect?d(l.rect,l.childViews,[n.translateX||0,n.translateY||0],[n.scaleX||0,n.scaleY||0]):null};return r.rect={get:c},a.rect={get:c},t.forEach(function(e){n[e]=void 0===s[e]?b[e]:s[e]}),{write:function(){if(function(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!0;for(var n in t)if(t[n]!==e[n])return!0;return!1}(u,n))return function(e,t){var n=t.opacity,r=t.translateX,o=t.translateY,i=t.scaleX,a=t.scaleY,l=t.rotateX,s=t.rotateY,u=t.rotateZ,c=t.height,f=[],p=[];(T(r)||T(o))&&f.push("translate3d("+(r||0)+"px, "+(o||0)+"px, 0)"),(T(i)||T(a))&&f.push("scale3d("+(T(i)?i:1)+", "+(T(a)?a:1)+", 1)"),(T(u)||T(s)||T(l))&&f.push("rotate3d("+(l||0)+", "+(s||0)+", "+(u||0)+", 360deg)"),f.length&&p.push("transform:"+f.join(" ")),T(n)&&(p.push("opacity:"+n),0===n&&p.push("visibility:hidden"),n<1&&p.push("pointer-events:none;")),T(c)&&p.push("height:"+c+"px");var d=e.getAttribute("style")||"",m=p.join(";");m.length===d.length&&m===d||e.setAttribute("style",m)}(l.element,n),Object.assign.apply(Object,[u].concat(i(n))),!0},destroy:function(){}}},listeners:function(e){e.mixinConfig,e.viewProps,e.viewInternalAPI;var t,n=e.viewExternalAPI,r=(e.viewState,e.view),o=[],i=(t=r.element,function(e,n){t.addEventListener(e,n)}),a=function(e){return function(t,n){e.removeEventListener(t,n)}}(r.element);return n.on=function(e,t){o.push({type:e,fn:t}),i(e,t)},n.off=function(e,t){o.splice(o.findIndex(function(n){return n.type===e&&n.fn===t}),1),a(e,t)},{write:function(){return!0},destroy:function(){o.forEach(function(e){a(e.type,e.fn)})}}},animations:function(e){var t=e.mixinConfig,n=e.viewProps,r=e.viewInternalAPI,i=e.viewExternalAPI,l=o({},n),s=[],u=0;return a(t,function(e,t){var o=_(t);o&&(o.onupdate=function(t){n[e]=t},o.oncomplete=function(){u--},u++,o.target=l[e],I([{key:e,setter:function(e){o.target!==e&&(o.resting&&u++,o.target=e)},getter:function(){return n[e]}}],[r,i],n,!0),s.push(o))}),{write:function(e){return s.forEach(function(t){t.interpolate(e)}),0===u},destroy:function(){}}},apis:function(e){var t=e.mixinConfig,n=e.viewProps,r=e.viewExternalAPI;I(t,r,n)}},w=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.paddingTop=parseInt(n.paddingTop,10)||0,e.marginTop=parseInt(n.marginTop,10)||0,e.marginRight=parseInt(n.marginRight,10)||0,e.marginBottom=parseInt(n.marginBottom,10)||0,e.marginLeft=parseInt(n.marginLeft,10)||0,e.left=t.offsetLeft||0,e.top=t.offsetTop||0,e.width=t.offsetWidth||0,e.height=t.offsetHeight||0,e.right=e.left+e.width,e.bottom=e.top+e.height,e.scrollTop=t.scrollTop,e.hidden=null===t.offsetParent,e},D=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.tag,n=void 0===t?"div":t,r=e.name,i=void 0===r?null:r,l=e.attributes,u=void 0===l?{}:l,c=e.read,f=void 0===c?function(){}:c,m=e.write,v=void 0===m?function(){}:m,h=e.create,E=void 0===h?function(){}:h,g=e.destroy,_=void 0===g?function(){}:g,I=e.filterFrameActionsForChild,y=void 0===I?function(e,t){return t}:I,T=e.didCreateView,b=void 0===T?function(){}:T,D=e.ignoreRect,O=void 0!==D&&D,S=e.mixins,A=void 0===S?[]:S;return function(e){var t,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},l=p(n,"filepond--"+i,u),c=window.getComputedStyle(l,null),m=w(),h=null,g=[],I=[],T={},D={},S=[v],M=[f],L=[_],P=function(){return l},C=function(){return[].concat(g)},N=function(){return h||(h=d(m,g,[0,0],[1,1]))},x={element:{get:P},style:{get:function(){return c}},childViews:{get:C}},G=o({},x,{rect:{get:N},ref:{get:function(){return T}},is:function(e){return i===e},appendChild:(t=l,function(e,n){void 0!==n&&t.children[n]?t.insertBefore(e,t.children[n]):t.appendChild(e)}),createChildView:function(e){return function(t,n){return t(e,n)}}(e),appendChildView:function(e,t){return function(e,n){return void 0!==n?t.splice(n,0,e):t.push(e),e}}(0,g),removeChildView:function(e,t){return function(n){return t.splice(t.indexOf(n),1),n.element.parentNode&&e.removeChild(n.element),n}}(l,g),registerWriter:function(e){return S.push(e)},registerReader:function(e){return M.push(e)},dispatch:e.dispatch,query:e.query}),B={element:{get:P},childViews:{get:C},rect:{get:N},isRectIgnored:function(){return O},_read:function(){h=null,g.forEach(function(e){return e._read()}),w(m,l,c),M.forEach(function(e){return e({root:F,props:r,rect:m})})},_write:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=0===t.length;return S.forEach(function(o){!1===o({props:r,root:F,actions:t,timestamp:e})&&(n=!1)}),I.forEach(function(t){!1===t.write(e)&&(n=!1)}),g.filter(function(e){return!!e.element.parentNode}).forEach(function(r){r._write(e,y(r,t))||(n=!1)}),g.filter(function(e){return!e.element.parentNode}).forEach(function(r,o){F.appendChild(r.element,o),r._read(),r._write(e,y(r,t)),n=!1}),n},_destroy:function(){I.forEach(function(e){return e.destroy()}),L.forEach(function(e){return e({root:F})}),g.forEach(function(e){return e._destroy()})}},k=o({},x,{rect:{get:function(){return m}}});a(A,function(e,t){var n=R[e]({mixinConfig:t,viewProps:r,viewState:D,viewInternalAPI:G,viewExternalAPI:B,view:s(k)});n&&I.push(n)});var F=s(G);E({root:F,props:r});var U=l.children.length;return g.forEach(function(e,t){F.appendChild(e.element,U+t)}),b(F),s(B)}},O=function(e){return function(t){var n=t.root,r=t.props,o=t.actions;(void 0===o?[]:o).filter(function(t){return e[t.type]}).forEach(function(t){return e[t.type]({root:n,props:r,action:t.data})})}},S=function(e,t){return t.parentNode.insertBefore(e,t)},A=function(e,t){return t.parentNode.insertBefore(e,t.nextSibling)},M=function(e){return Array.isArray(e)},L=function(e){return e.trim()},P=function(e){return""+e},C=function(e){return"boolean"==typeof e},N=function(e){return C(e)?e:"true"===e},x=function(e){return"string"==typeof e},G=function(e){return h(e)?e:x(e)?P(e).replace(/[a-z]+/gi,""):0},B=function(e){return parseInt(G(e),10)},k=function(e){return h(e)&&isFinite(e)&&Math.floor(e)===e},F=function(e){if(k(e))return e;var t=P(e).trim();return/MB$/i.test(t)?(t=t.replace(/MB$i/,"").trim(),1e3*B(t)*1e3):/KB/i.test(t)?(t=t.replace(/KB$i/,"").trim(),1e3*B(t)):B(t)},U={process:"POST",revert:"DELETE",fetch:"GET",restore:"GET",load:"GET"},V=function(e){return"object"===(void 0===e?"undefined":r(e))&&null!==e},q=function(e){return M(e)?"array":function(e){return null===e}(e)?"null":k(e)?"int":/^[0-9]+ ?(?:GB|MB|KB)$/gi.test(e)?"bytes":function(e){return V(e)&&x(e.url)&&V(e.process)&&V(e.revert)&&V(e.restore)&&V(e.fetch)}(e)?"api":void 0===e?"undefined":r(e)},Y={array:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:",";return y(e)?[]:M(e)?e:P(e).split(t).map(L).filter(function(e){return e.length})},boolean:N,int:function(e){return"bytes"===q(e)?F(e):B(e)},float:function(e){return parseFloat(G(e))},bytes:F,string:P,serverapi:function(e){return(n={}).url=x(t=e)?t:t.url||"",n.timeout=t.timeout?parseInt(t.timeout,10):7e3,a(U,function(e){n[e]=function(e,t,n,r){if(null===t)return null;if("function"==typeof t)return t;var o={url:"GET"===n?"?"+e+"=":"",method:n,headers:{},withCredentials:!1,timeout:r};if(x(t))return o.url=t,o;if(Object.assign(o,t),x(o.headers)){var i=o.headers.split(/:(.+)/);o.headers={header:i[0],value:i[1]}}return o.withCredentials=N(o.withCredentials),o}(e,t[e],U[e],n.timeout)}),n;var t,n},function:function(e){return function(e){for(var t=self,n=e.split("."),r=null;r=n.shift();)if(!(t=t[r]))return null;return t}(e)}},X=function(e,t,n){if(e===t)return e;var r,o=q(e);if(o!==n){var i=(r=e,Y[n](r));if(o=q(i),null===i)throw'Trying to assign value with incorrect type to "'+option+'", allowed type: "'+n+'"';e=i}return e},z=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"-";return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(t)},j=function(){return Math.random().toString(36).substr(2,9)},W=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:75;return e.map(function(e,r){return new Promise(function(o,i){setTimeout(function(){t(e),o()},n*r)})})},H=function(e,t){return e.splice(t,1)},$=function(){var e=[],t=function(t,n){H(e,e.findIndex(function(e){return e.event===t&&(e.cb===n||!n)}))};return{fire:function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;oBrowse',te.STRING],labelFileWaitingForSize:["Waiting for size",te.STRING],labelFileSizeNotAvailable:["Size not available",te.STRING],labelFileCountSingular:["file in list",te.STRING],labelFileCountPlural:["files in list",te.STRING],labelFileLoading:["Loading",te.STRING],labelFileAdded:["Added",te.STRING],labelFileRemoved:["Removed",te.STRING],labelFileLoadError:["Error during load",te.STRING],labelFileProcessing:["Uploading",te.STRING],labelFileProcessingComplete:["Upload complete",te.STRING],labelFileProcessingAborted:["Upload cancelled",te.STRING],labelFileProcessingError:["Error during upload",te.STRING],labelTapToCancel:["tap to cancel",te.STRING],labelTapToRetry:["tap to retry",te.STRING],labelTapToUndo:["tap to undo",te.STRING],labelButtonRemoveItem:["Remove",te.STRING],labelButtonAbortItemLoad:["Abort",te.STRING],labelButtonRetryItemLoad:["Retry",te.STRING],labelButtonAbortItemProcessing:["Cancel",te.STRING],labelButtonUndoItemProcessing:["Undo",te.STRING],labelButtonRetryItemProcessing:["Retry",te.STRING],labelButtonProcessItem:["Upload",te.STRING],iconRemove:['',te.STRING],iconProcess:['',te.STRING],iconRetry:['',te.STRING],iconUndo:['',te.STRING],iconDone:['',te.STRING],oninit:[null,te.FUNCTION],onwarning:[null,te.FUNCTION],onerror:[null,te.FUNCTION],onaddfilestart:[null,te.FUNCTION],onaddfileprogress:[null,te.FUNCTION],onaddfile:[null,te.FUNCTION],onprocessfilestart:[null,te.FUNCTION],onprocessfileprogress:[null,te.FUNCTION],onprocessfileabort:[null,te.FUNCTION],onprocessfilerevert:[null,te.FUNCTION],onprocessfile:[null,te.FUNCTION],onremovefile:[null,te.FUNCTION],files:[[],te.ARRAY]},se=function(e,t){return y(t)?e[0]||null:k(t)?e[t]||null:("object"===(void 0===t?"undefined":r(t))&&(t=t.id),e.find(function(e){return e.id===t})||null)},ue=function(e){return{GET_ITEM:function(t){return se(e.items,t)},GET_ITEMS:function(t){return[].concat(i(e.items))},GET_ITEM_NAME:function(t){var n=se(e.items,t);return n?n.filename:null},GET_ITEM_SIZE:function(t){var n=se(e.items,t);return n?n.fileSize:null},GET_TOTAL_ITEMS:function(){return e.items.length},IS_ASYNC:function(){return V(e.options.server)&&(V(e.options.server.process)||"function"==typeof e.options.server.process)}}},ce=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return(t+e).slice(-t.length)},fe=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:new Date;return e.getFullYear()+"-"+ce(e.getMonth()+1,"00")+"-"+ce(e.getDate(),"00")+"_"+ce(e.getHours(),"00")+"-"+ce(e.getMinutes(),"00")+"-"+ce(e.getSeconds(),"00")},pe=function(e){return/^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i.test(e)},de=function(e){return e.split("/").pop().split("?").shift()},me=function(e){return e.split(".").pop()},ve=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,o="string"==typeof n?e.slice(0,e.size,n):e.slice(0,e.size,e.type);return o.lastModifiedDate=new Date,t&&null===r&&me(t)?o.name=t:(r=r||function(e){if("string"!=typeof e)return"";var t=e.split("/").pop();return/svg/.test(t)?"svg":/zip|compressed/.test(t)?"zip":/plain/.test(t)?"txt":/msword/.test(t)?"doc":/[a-z]+/.test(t)?"jpeg"===t?"jpg":t:""}(o.type),o.name=t+(r?"."+r:"")),o},he=function(e){return(/^data:(.+);/.exec(e)||[])[1]||null},Ee=function(e){var t=he(e);return function(e,t){for(var n=new ArrayBuffer(e.length),r=new Uint8Array(n),o=0;o=200&&u.status<300?r.onload(Ie("load",u.status,u.response,u.getAllResponseHeaders())):r.onerror(Ie("error",u.status,u.statusText))},u.onerror=function(){r.onerror(Ie("error",u.status,u.statusText))},u.onabort=function(){a||(l=!0,r.onabort())},k(n.timeout)&&(i=setTimeout(function(){a=!0,r.onerror(Ie("error",0,"timeout")),r.abort()},n.timeout)),u.open(n.method,t,!0),Object.keys(n.headers).forEach(function(e){u.setRequestHeader(e,n.headers[e])}),n.responseType&&(u.responseType=n.responseType),n.withCredentials&&(u.withCredentials=!0),u.send(e),r},Te=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1];return"function"==typeof t?t:t&&x(t.url)?function(n,r,i,a,l,s){var u=ye(n,e+t.url,o({},t,{responseType:"blob"}));return u.onload=function(e){e.body=ve(e.body,ge(e.headers)||de(n)||fe()),r(e)},u.onerror=i,u.onprogress=a,u.onabort=l,u.onheaders=s,u}:null},be=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;return e+Math.random()*(t-e)},Re=function(e){var t={complete:!1,perceivedProgress:0,perceivedPerformanceUpdater:null,progress:null,timestamp:null,perceivedDuration:0,duration:0,request:null,response:null},n=function(){t.request&&(t.perceivedPerformanceUpdater.clear(),t.request.abort(),t.complete=!0,r.fire("abort",t.response?t.response.body:null))},r=o({},$(),{process:function(n,o){var i=function(){0!==t.duration&&null!==t.progress&&r.fire("progress",r.getProgress())},a=function(){t.complete=!0,r.fire("load",t.response.body)};r.fire("start"),t.timestamp=Date.now(),t.perceivedPerformanceUpdater=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1e3,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:25,r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:250,o=null,i=Date.now();return function a(){var l=Date.now()-i,s=be(n,r);l+s>t&&(s=l+s-t);var u=l/t;u>=1?e(1):(e(u),o=setTimeout(a,s))}(),{clear:function(){clearTimeout(o)}}}(function(e){t.perceivedProgress=e,t.perceivedDuration=Date.now()-t.timestamp,i(),1===e&&t.response&&!t.complete&&a()},be(750,1500)),t.request=e(n,o,function(e){t.response="string"==typeof e?{type:"load",code:200,body:e,headers:{}}:e,t.duration=Date.now()-t.timestamp,t.progress=1,1===t.perceivedProgress&&a()},function(e){t.perceivedPerformanceUpdater.clear(),r.fire("error","string"==typeof e?{type:"error",code:0,body:e}:e)},function(e,n,r){t.duration=Date.now()-t.timestamp,t.progress=e?n/r:null,i()},function(){t.perceivedPerformanceUpdater.clear(),r.fire("abort")})},abort:n,getProgress:function(){return t.progress?Math.min(t.progress,t.perceivedProgress):null},getDuration:function(){return Math.min(t.duration,t.perceivedDuration)},reset:function(){n(),t.complete=!1,t.perceivedProgress=0,t.progress=0,t.timestamp=null,t.perceivedDuration=0,t.duration=0,t.request=null,t.response=null}});return r},we=function(e){return e.substr(0,e.lastIndexOf("."))||e},De={INIT:1,IDLE:2,PROCESSING:3,PROCESSING_PAUSED:4,PROCESSING_COMPLETE:5,PROCESSING_ERROR:6,LOADING:7,LOAD_ERROR:8},Oe=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=j(),n={source:null,file:null,serverFileReference:e,status:e?De.PROCESSING_COMPLETE:De.INIT,activeLoader:null,activeProcessor:null},r={},i=function(e){return n.status=e},a=o({id:{get:function(){return t}},serverId:{get:function(){return n.serverFileReference}},status:{get:function(){return n.status}},filename:{get:function(){return n.file.name}},filenameWithoutExtension:{get:function(){return we(n.file.name)}},fileExtension:{get:function(){return me(n.file.name)}},fileType:{get:function(){return n.file.type}},fileSize:{get:function(){return n.file.size}},file:{get:function(){return n.file}},source:{get:function(){return n.source}},getMetadata:function(e){return e?r[e]:o({},r)},setMetadata:function(e,t){return r[e]=t},abortLoad:function(){n.activeLoader&&n.activeLoader.abort()},retryLoad:function(){n.activeLoader&&n.activeLoader.load()},abortProcessing:function(){n.activeProcessor&&n.activeProcessor.abort()},load:function(e,t,r){n.source=e,n.file=function(e){var t=[e.name,e.size,e.type];return e instanceof Blob||pe(e)?t[0]=fe():pe(e)?(t[1]=e.length,t[2]=he(e)):e instanceof File||(t[0]=de(e),t[1]=0,t[2]="application/octet-stream"),{name:t[0],size:t[1],type:t[2]}}(e),t.on("init",function(){a.fire("load-init")}),t.on("meta",function(e){n.file.size=e.size,n.file.filename=e.filename,a.fire("load-meta")}),t.on("progress",function(e){i(De.LOADING),a.fire("load-progress",e)}),t.on("error",function(e){i(De.LOAD_ERROR),a.fire("load-request-error",e)}),t.on("abort",function(){i(De.INIT),a.fire("load-abort")}),t.on("load",function(e){n.activeLoader=null;var t=function(e){n.file=e,i(De.IDLE),a.fire("load")};n.serverFileReference?t(e):r(e,t,function(t){n.file=e,a.fire("load-meta"),i(De.LOAD_ERROR),a.fire("load-file-error",t)})}),t.setSource(e),n.activeLoader=t,t.load()},process:function e(t,l){n.file instanceof Blob?(t.on("load",function(e){n.activeProcessor=null,n.serverFileReference=e,i(De.PROCESSING_COMPLETE),a.fire("process-complete",e)}),t.on("start",function(){a.fire("process-start")}),t.on("error",function(e){n.activeProcessor=null,i(De.PROCESSING_ERROR),a.fire("process-error",e)}),t.on("abort",function(e){n.activeProcessor=null,n.serverFileReference=e,i(De.IDLE),a.fire("process-abort")}),t.on("progress",function(e){i(De.PROCESSING),a.fire("process-progress",e)}),l(n.file,function(e){t.process(e,o({},r))},function(e){}),n.activeProcessor=t):a.on("load",function(){e(t,l)})},revert:function(e){null!==n.serverFileReference&&(e(n.serverFileReference,function(){n.serverFileReference=null},function(e){}),i(De.IDLE),a.fire("process-revert"))}},$());return s(a)},Se=function(e,t,n,r,o,i){var a=ye(null,e,{method:"GET",responseType:"blob"});return a.onload=function(n){n.body=ve(n.body,ge(n.headers)||de(e)||fe()),t(n)},a.onerror=n,a.onprogress=r,a.onabort=o,a.onheaders=i,a},Ae=function(e){return 0===e.indexOf("//")&&(e=location.protocol+e),e.toLowerCase().replace(/([a-z])?:\/\//,"$1").split("/")[0]},Me=function(e,t){return function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=n.query,o=n.success,i=void 0===o?function(){}:o,a=n.failure,l=void 0===a?function(){}:a,s=se(e.items,r);s?t(s,i,l):l({error:Ie("error",0,"Item not found"),file:null})}},Le=function(e,t,n){return{ABORT_ALL:function(){t("GET_ITEMS").forEach(function(e){e.abortLoad(),e.abortProcessing()})},DID_SET_FILES:function(t){var r=t.value,a=(void 0===r?[]:r).map(function(e){return{source:e.source?e.source:e,options:e.options}});[].concat(i(n.items)).forEach(function(t){a.find(function(e){return e.source===t.source})||e("REMOVE_ITEM",{query:t})}),a.forEach(function(t,r){[].concat(i(n.items)).find(function(e){return e.source===t.source})||e("ADD_ITEM",o({},t,{interactionMethod:5,index:r}))})},ADD_ITEM:function(r){var i=r.source,a=r.index,l=r.interactionMethod,s=r.success,u=void 0===s?function(){}:s,c=r.failure,f=void 0===c?function(){}:c,p=r.options,d=void 0===p?{}:p;if(y(i))f({error:Ie("error",0,"No source"),file:null});else if(!(i instanceof Blob&&n.options.ignoredFiles.includes(i.name.toLowerCase()))){if(!function(e){var t=e.items.length;if(!e.options.allowMultiple)return 0===t;var n=e.options.maxFiles;return null===n||t=400&&t.code<500)return e("DID_THROW_ITEM_INVALID",{id:_,error:t,status:{main:n.options.labelFileLoadError,sub:t.code+" ("+t.body+")"}}),void f({error:t,file:J(g)});e("DID_THROW_ITEM_LOAD_ERROR",{id:_,error:t,status:{main:n.options.labelFileLoadError,sub:n.options.labelTapToRetry}})}),g.on("load-file-error",function(t){e("DID_THROW_ITEM_INVALID",o({},t,{id:_}))}),g.on("load-abort",function(){e("REMOVE_ITEM",{query:_})}),g.on("load",function(){re("DID_LOAD_ITEM",g,{query:t}).then(function(){re("SHOULD_PREPARE_OUTPUT",!1,{item:g,query:t}).then(function(t){var n={isServerFile:E,source:i,isLocalServerFile:v,isLimboServerFile:h,success:u};t?e("REQUEST_PREPARE_LOAD_ITEM",{query:_,item:g,data:n},!0):e("COMPLETE_LOAD_ITEM",{query:_,item:g,data:n})})})}),g.on("process-start",function(){e("DID_START_ITEM_PROCESSING",{id:_})}),g.on("process-progress",function(t){e("DID_UPDATE_ITEM_PROCESS_PROGRESS",{id:_,progress:t})}),g.on("process-error",function(t){e("DID_THROW_ITEM_PROCESSING_ERROR",{id:_,error:t,status:{main:n.options.labelFileProcessingError,sub:n.options.labelTapToRetry}})}),g.on("process-abort",function(t){n.options.instantUpload?e("REMOVE_ITEM",{query:_}):e("DID_ABORT_ITEM_PROCESSING",{id:_}),e("REVERT_ITEM_PROCESSING",{query:_})}),g.on("process-complete",function(t){e("DID_COMPLETE_ITEM_PROCESSING",{id:_,error:null,serverFileReference:t})}),g.on("process-revert",function(){n.options.instantUpload?e("REMOVE_ITEM",{query:_}):e("DID_REVERT_ITEM_PROCESSING",{id:_})}),e("DID_ADD_ITEM",{id:_,index:a,interactionMethod:l});var I=n.options.server||{},T=I.url,b=I.load,R=I.restore,w=I.fetch;g.load(i,function(e){var t={source:null,complete:!1,progress:0,size:null,timestamp:null,duration:0,request:null},n=function(n){e?(t.timestamp=Date.now(),t.request=e(n,function(e){t.duration=Date.now()-t.timestamp,t.complete=!0,e instanceof Blob&&(e=ve(e,de(n)||fe())),r.fire("load",e instanceof Blob?e:e.body)},function(e){r.fire("error","string"==typeof e?{type:"error",code:0,body:e}:e)},function(e,n,o){o&&(t.size=o),t.duration=Date.now()-t.timestamp,e?(t.progress=n/o,r.fire("progress",t.progress)):t.progress=null},function(){r.fire("abort")},function(e){r.fire("meta",{size:t.size,filename:ge("string"==typeof e?e:e.headers)})})):r.fire("error",{type:"error",body:"Can't load URL",code:400})},r=o({},$(),{setSource:function(e){return t.source=e},getProgress:function(){return t.progress},abort:function(){t.request&&t.request.abort()},load:function(){var e,o,i=t.source;r.fire("init",i),i instanceof File?r.fire("load",i):i instanceof Blob?r.fire("load",ve(i,fe())):pe(i)?r.fire("load",(e=i,o=fe(),ve(Ee(e),o,null,void 0))):n(i)}});return r}(E?"limbo"===d.type?Te(T,R):Te(T,b):x(i)&&function(e){return(e.indexOf(":")>-1||e.indexOf("//")>-1)&&Ae(location.href)!==Ae(e)}(i)?Te(T,w):Se),function(e,n,r){re("LOAD_FILE",e,{query:t}).then(n).catch(r)})}},REQUEST_PREPARE_LOAD_ITEM:function(n){var r=n.item,o=n.data;re("PREPARE_OUTPUT",r.file,{query:t,item:r}).then(function(n){re("COMPLETE_PREPARE_OUTPUT",n,{query:t,item:r}).then(function(t){e("COMPLETE_LOAD_ITEM",{item:r,data:o})})})},COMPLETE_LOAD_ITEM:function(r){var o=r.item,i=r.data,a=i.success,l=i.isServerFile,s=i.source,u=i.isLocalServerFile,c=i.isLimboServerFile;e("DID_LOAD_ITEM",{id:o.id,error:null,serverFileReference:l?s:null}),a(J(o)),u?e("DID_LOAD_LOCAL_ITEM",{id:o.id}):c?e("DID_COMPLETE_ITEM_PROCESSING",{id:o.id,error:null,serverFileReference:s}):t("IS_ASYNC")&&n.options.instantUpload&&e("REQUEST_ITEM_PROCESSING",{query:o.id})},RETRY_ITEM_LOAD:Me(n,function(e){e.retryLoad()}),REQUEST_ITEM_PROCESSING:Me(n,function(t){var n=t.id;e("DID_REQUEST_ITEM_PROCESSING",{id:n}),e("PROCESS_ITEM",{query:t},!0)}),PROCESS_ITEM:Me(n,function(e,r,o){e.onOnce("process-complete",function(){r(J(e))}),e.onOnce("process-error",function(t){o({error:t,file:J(e)})}),e.process(Re(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1],n=arguments[2];return"function"==typeof t?function(){for(var e=arguments.length,r=Array(e),o=0;o0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1];return"function"==typeof t?t:t&&x(t.url)?function(n,r,o){var i=ye(n,e+t.url,t);return i.onload=r,i.onerror=o,i}:function(e,t){return t()}}(n.options.server.url,n.options.server.revert))}),SET_OPTIONS:function(t){var n=t.options;a(n,function(t,n){e("SET_"+z(t,"_").toUpperCase(),{value:n})})}}},Pe=function(e){return document.createElement(e)},Ce=function(e){return decodeURI(e)},Ne=function(e,t){var n=e.childNodes[0];n?t!==n.nodeValue&&(n.nodeValue=t):(n=document.createTextNode(t),e.appendChild(n))},xe=function(e,t,n,r){var o=(r%360-90)*Math.PI/180;return{x:e+n*Math.cos(o),y:t+n*Math.sin(o)}},Ge=D({tag:"div",name:"progress-indicator",ignoreRect:!0,create:function(e){var t=e.root,n=e.props;n.spin=!1,n.progress=0,n.opacity=0;var r=p("svg");t.ref.path=p("path",{"stroke-width":2,"stroke-linecap":"round"}),r.appendChild(t.ref.path),t.ref.svg=r,t.appendChild(r)},write:function(e){var t=e.root,n=e.props;if(0!==n.opacity){var r=parseInt(u(t.ref.path,"stroke-width"),10),o=.5*t.rect.element.width,i=0,a=0;n.spin?(i=0,a=.5):(i=0,a=n.progress);var l=function(e,t,n,r,o){var i=1;return o>r&&o-r<=.5&&(i=0),r>o&&r-o>=.5&&(i=0),function(e,t,n,r,o,i){var a=xe(e,t,n,o),l=xe(e,t,n,r);return["M",a.x,a.y,"A",n,n,0,i,0,l.x,l.y].join(" ")}(e,t,n,360*Math.min(.9999,r),360*Math.min(.9999,o),i)}(o,o,o-r,i,a);u(t.ref.path,"d",l),u(t.ref.path,"stroke-opacity",n.spin||n.progress>0?1:0)}},mixins:{apis:["progress","spin"],styles:["opacity"],animations:{opacity:{type:"tween",duration:500},progress:{type:"spring",stiffness:.95,damping:.65,mass:10}}}}),Be=D({tag:"button",attributes:{type:"button"},ignoreRect:!0,name:"file-action-button",mixins:{apis:["label"],styles:["translateX","translateY","scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",translateX:"spring",translateY:"spring",opacity:{type:"tween",duration:250}},listeners:!0},create:function(e){var t=e.root,n=e.props;t.element.title=n.label,t.element.innerHTML=n.icon||"",n.disabled=!1},write:function(e){var t=e.root,n=e.props;0!==n.opacity||n.disabled?n.opacity>0&&n.disabled&&(n.disabled=!1,t.element.removeAttribute("disabled")):(n.disabled=!0,u(t.element,"disabled","disabled"))}}),ke=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:".";return(e=Math.round(Math.abs(e)))<1e3?e+" bytes":e-1?function(e,t,n){return e-1===t?10/6:e===t?5:e+1===t?-5:e+2===t?-10/6:0}(t,n.dragIndex):0),e.markedForRemoval||(e.scaleX=1,e.scaleY=1,e.opacity=1),i+=r.outer.height}),t.childViews.filter(function(e){return e.markedForRemoval&&0===e.opacity}).forEach(function(e){t.removeChildView(e),o=!1}),o},tag:"ul",name:"list",filterFrameActionsForChild:function(e,t){return t.filter(function(t){return!t.data||!t.data.id||e.id===t.data.id})},mixins:{apis:["dragIndex"]}}),_t=function(e,t){for(var n=0,r=e.childViews,o=r.length;n3&&void 0!==arguments[3]?arguments[3]:"";n?u(e,t,r):e.removeAttribute(t)},bt=function(e){var t=e.root;t.query("GET_TOTAL_ITEMS")>0?Tt(t.element,"required",!1):t.query("GET_REQUIRED")&&Tt(t.element,"required",!0)},Rt=D({tag:"input",name:"browser",ignoreRect:!0,attributes:{type:"file"},create:function(e){var t=e.root,n=e.props;t.element.id="filepond--browser-"+n.id,u(t.element,"aria-controls","filepond--assistant-"+n.id),u(t.element,"aria-labelledby","filepond--drop-label-"+n.id),t.element.addEventListener("change",function(){if(t.element.value){var e=[].concat(i(t.element.files));setTimeout(function(){n.onload(e),function(e){if(e&&""!==e.value){try{e.value=""}catch(e){}if(e.value){var t=Pe("form"),n=e.parentNode,r=e.nextSibling;t.appendChild(e),t.reset(),r?n.insertBefore(e,r):n.appendChild(e)}}}(t.element)},250)}})},write:O({DID_ADD_ITEM:bt,DID_REMOVE_ITEM:bt,DID_SET_ALLOW_BROWSE:function(e){var t=e.root,n=e.action;Tt(t.element,"disabled",!n.value)},DID_SET_ALLOW_MULTIPLE:function(e){var t=e.root,n=e.action;Tt(t.element,"multiple",n.value)},DID_SET_ACCEPTED_FILE_TYPES:function(e){var t=e.root,n=e.action;Tt(t.element,"accept",!!n.value,n.value?n.value.join(","):"")},DID_SET_CAPTURE_METHOD:function(e){var t=e.root,n=e.action;Tt(t.element,"capture",!!n.value,!0===n.value?"":n.value)},DID_SET_REQUIRED:function(e){var t=e.root;e.action.value?0===t.query("GET_TOTAL_ITEMS")&&Tt(t.element,"required",!0):Tt(t.element,"required",!1)}})}),wt=D({name:"drop-label",create:function(e){var t=e.root,n=e.props,r=Pe("label");u(r,"for","filepond--browser-"+n.id),u(r,"id","filepond--drop-label-"+n.id),u(r,"aria-hidden","true"),r.addEventListener("keydown",function(e){13!==e.keyCode&&32!==e.keyCode||(e.preventDefault(),t.ref.label.click())}),t.appendChild(r),t.ref.label=r},write:O({DID_SET_LABEL_IDLE:function(e){var t=e.root,n=e.action;e.props.caption=function(e,t){e.innerHTML=t;var n=e.querySelector(".filepond--label-action");return n&&u(n,"tabindex","0"),t}(t.ref.label,n.value)}}),mixins:{apis:["caption"],styles:["opacity","translateX","translateY"],animations:{opacity:{type:"tween",duration:150},translateX:"spring",translateY:"spring"}}}),Dt=D({name:"drip-blob",ignoreRect:!0,mixins:{styles:["translateX","translateY","scaleX","scaleY","opacity"],animations:{scaleX:"spring",scaleY:"spring",translateX:"spring",translateY:"spring",opacity:{type:"tween",duration:250}}}}),Ot=O({DID_DRAG:function(e){var t=e.root,n=e.action;t.ref.blob?(t.ref.blob.translateX=n.position.scopeLeft,t.ref.blob.translateY=n.position.scopeTop,t.ref.blob.scaleX=1,t.ref.blob.scaleY=1,t.ref.blob.opacity=1):function(e){var t=e.root,n=.5*t.rect.element.width,r=.5*t.rect.element.height;t.ref.blob=t.appendChildView(t.createChildView(Dt,{opacity:0,scaleX:2.5,scaleY:2.5,translateX:n,translateY:r}))}({root:t})},DID_DROP:function(e){var t=e.root;t.ref.blob&&(t.ref.blob.scaleX=2.5,t.ref.blob.scaleY=2.5,t.ref.blob.opacity=0)},DID_END_DRAG:function(e){var t=e.root;t.ref.blob&&(t.ref.blob.opacity=0)}}),St=D({ignoreRect:!0,name:"drip",write:function(e){var t=e.root,n=e.props,r=e.actions;Ot({root:t,props:n,actions:r});var o=t.ref.blob;0===r.length&&o&&0===o.opacity&&(t.removeChildView(o),t.ref.blob=null)}}),At=function(e){return new Promise(function(t,n){var r=Bt(e);r.length?t(r):Mt(e).then(t)})},Mt=function(e){return new Promise(function(t,n){var r=(e.items?[].concat(i(e.items)):[]).filter(function(e){return Lt(e)}).map(function(e){return Pt(e)});r.length?Promise.all(r).then(function(e){var n=[];e.forEach(function(e){n.push.apply(n,i(e))}),t(n.filter(function(e){return e}))}):t([].concat(i(e.files)))})},Lt=function(e){if(xt(e)){var t=Gt(e);if(t)return t.isFile||t.isDirectory}return"file"===e.kind},Pt=function(e){return new Promise(function(t,n){Nt(e)?Ct(Gt(e)).then(t):t([e.getAsFile()])})},Ct=function(e){return new Promise(function(t,n){var r=[],o=0;!function e(n){n.createReader().readEntries(function(n){n.forEach(function(n){n.isDirectory?e(n):(o++,n.file(function(e){r.push(e),o===r.length&&t(r)}))})})}(e)})},Nt=function(e){return xt(e)&&(Gt(e)||{}).isDirectory},xt=function(e){return"webkitGetAsEntry"in e},Gt=function(e){return e.webkitGetAsEntry()},Bt=function(e){var t=[];try{if((t=Ft(e)).length)return t;t=kt(e)}catch(e){}return t},kt=function(e){var t=e.getData("url");return"string"==typeof t&&t.length?[t]:[]},Ft=function(e){var t=e.getData("text/html");if("string"==typeof t&&t.length){var n=t.match(/src\s*=\s*"(.+?)"/);if(n)return[n[1]]}return[]},Ut=[],Vt=function(e){return{pageLeft:e.pageX,pageTop:e.pageY,scopeLeft:e.layerX||e.offsetX,scopeTop:e.layerY||e.offsetY}},qt=function(e){var t=[],n={dragenter:jt,dragover:Wt,dragleave:$t,drop:Ht},r={};a(n,function(n,o){r[n]=o(e,t),e.addEventListener(n,r[n],!1)});var o={element:e,addListener:function(i){return t.push(i),function(){t.splice(t.indexOf(i),1),0===t.length&&(Ut.splice(Ut.indexOf(o),1),a(n,function(t){e.removeEventListener(t,r[t],!1)}))}}};return o},Yt=function(e,t){var n,r=("getRootNode"in(n=t)?n.getRootNode():document).elementFromPoint(e.pageX-window.pageXOffset,e.pageY-window.pageYOffset);return r===t||t.contains(r)},Xt=null,zt=function(e,t){try{e.dropEffect=t}catch(e){}},jt=function(e,t){return function(e){e.preventDefault(),Xt=e.target,t.forEach(function(t){var n=t.element,r=t.onenter;Yt(e,n)&&(t.state="enter",r(Vt(e)))})}},Wt=function(e,t){return function(e){e.preventDefault();var n=e.dataTransfer;At(n).then(function(r){var o=!1;t.some(function(t){var i=t.filterElement,a=t.element,l=t.onenter,s=t.onexit,u=t.ondrag,c=t.allowdrop;zt(n,"copy");var f=c(r);if(f)if(Yt(e,a)){if(o=!0,null===t.state)return t.state="enter",void l(Vt(e));if(t.state="over",i&&!f)return void zt(n,"none");u(Vt(e))}else i&&!o&&zt(n,"none"),t.state&&(t.state=null,s(Vt(e)));else zt(n,"none")})})}},Ht=function(e,t){return function(e){e.preventDefault();var n=e.dataTransfer;At(n).then(function(n){t.forEach(function(t){var r=t.filterElement,o=t.element,i=t.ondrop,a=t.onexit,l=t.allowdrop;t.state=null,l(n)?r&&!Yt(e,o)||i(Vt(e),n):a(Vt(e))})})}},$t=function(e,t){return function(e){Xt===e.target&&t.forEach(function(t){var n=t.onexit;t.state=null,n(Vt(e))})}},Qt=function(e,t,n){e.classList.add("filepond--hopper");var r=n.catchesDropsOnPage,o=n.requiresDropOnElement,i=function(e,t,n){var r=function(e){var t=Ut.find(function(t){return t.element===e});if(t)return t;var n=qt(e);return Ut.push(n),n}(t),o={element:e,filterElement:n,state:null,ondrop:function(){},onenter:function(){},ondrag:function(){},onexit:function(){},onload:function(){},allowdrop:function(){}};return o.destroy=r.addListener(o),o}(e,r?document.documentElement:e,o),a="",l="";i.allowdrop=function(e){return t(e)},i.ondrop=function(e,n){t(n)?(l="drag-drop",s.onload(n,e)):s.ondragend(e)},i.ondrag=function(e){s.ondrag(e)},i.onenter=function(e){l="drag-over",s.ondragstart(e)},i.onexit=function(e){l="drag-exit",s.ondragend(e)};var s={updateHopperState:function(){a!==l&&(e.dataset.hopperState=l,a=l)},onload:function(){},ondragstart:function(){},ondrag:function(){},ondragend:function(){},destroy:function(){i.destroy()}};return s},Zt=!1,Jt=[],Kt=function(e){At(e.clipboardData).then(function(e){e.length&&Jt.forEach(function(t){return t(e)})})},en=null,tn=null,nn=[],rn=function(e,t){e.element.textContent=t},on=function(e,t,n){var r=e.query("GET_TOTAL_ITEMS");rn(e,n+" "+t+", "+r+" "+(1===r?e.query("GET_LABEL_FILE_COUNT_SINGULAR"):e.query("GET_LABEL_FILE_COUNT_PLURAL"))),clearTimeout(tn),tn=setTimeout(function(){!function(e){e.element.textContent=""}(e)},1500)},an=function(e){return e.element.parentNode.contains(document.activeElement)},ln=function(e){var t=e.root,n=e.action,r=t.query("GET_ITEM",n.id).filename,o=t.query("GET_LABEL_FILE_PROCESSING_ABORTED");rn(t,r+" "+o)},sn=function(e){var t=e.root,n=e.action,r=t.query("GET_ITEM",n.id).filename;rn(t,n.status.main+" "+r+" "+n.status.sub)},un=D({create:function(e){var t=e.root,n=e.props;t.element.id="filepond--assistant-"+n.id,u(t.element,"role","status"),u(t.element,"aria-live","polite"),u(t.element,"aria-relevant","additions")},ignoreRect:!0,write:O({DID_LOAD_ITEM:function(e){var t=e.root,n=e.action;if(an(t)){t.element.textContent="";var r=t.query("GET_ITEM",n.id);nn.push(r.filename),clearTimeout(en),en=setTimeout(function(){on(t,nn.join(", "),t.query("GET_LABEL_FILE_ADDED")),nn.length=0},750)}},DID_REMOVE_ITEM:function(e){var t=e.root,n=e.action;if(an(t)){var r=n.item;on(t,r.filename,t.query("GET_LABEL_FILE_REMOVED"))}},DID_COMPLETE_ITEM_PROCESSING:function(e){var t=e.root,n=e.action,r=t.query("GET_ITEM",n.id).filename,o=t.query("GET_LABEL_FILE_PROCESSING_COMPLETE");rn(t,r+" "+o)},DID_ABORT_ITEM_PROCESSING:ln,DID_REVERT_ITEM_PROCESSING:ln,DID_THROW_ITEM_LOAD_ERROR:sn,DID_THROW_ITEM_INVALID:sn,DID_THROW_ITEM_PROCESSING_ERROR:sn}),tag:"span",name:"assistant"}),cn=O({DID_SET_ALLOW_BROWSE:function(e){var t=e.root,n=e.props;e.action.value?t.ref.browser=t.appendChildView(t.createChildView(Rt,o({},n,{onload:function(e){W(e,function(e){t.dispatch("ADD_ITEM",{interactionMethod:3,source:e,index:0})})}})),0):t.ref.browser&&t.removeChildView(t.ref.browser)},DID_SET_ALLOW_DROP:function(e){var t=e.root,n=(e.props,e.action);if(n.value&&!t.ref.hopper){var r=Qt(t.element,function(e){var n=t.query("GET_ALLOW_REPLACE"),r=t.query("GET_ALLOW_MULTIPLE"),o=t.query("GET_TOTAL_ITEMS"),i=t.query("GET_DROP_VALIDATION"),a=t.query("GET_MAX_TOTAL_ITEMS"),l=e.length;return!(!r&&l>1)&&!(k(a=r?a:n?a:1)&&o+l>a)&&(!i||e.every(function(e){return oe("ALLOW_HOPPER_ITEM",e,{query:t.query}).every(function(e){return!0===e})}))},{catchesDropsOnPage:t.query("GET_DROP_ON_PAGE"),requiresDropOnElement:t.query("GET_DROP_ON_ELEMENT")});r.onload=function(e,n){var r=t.ref.list.childViews[0],o=_t(r,{left:n.scopeLeft,top:n.scopeTop-t.ref.list.rect.outer.top+t.ref.list.element.scrollTop});W(e,function(e){t.dispatch("ADD_ITEM",{interactionMethod:2,source:e,index:o})}),t.dispatch("DID_DROP",{position:n}),t.dispatch("DID_END_DRAG",{position:n})},r.ondragstart=function(e){t.dispatch("DID_START_DRAG",{position:e})},r.ondrag=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:16,n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],r=Date.now(),o=null;return function(){for(var i=arguments.length,a=Array(i),l=0;le&&(e=n),e},0)}(v),y=f>0?.5*t.rect.element.paddingTop:0;if(g.fixedHeight)u.scalable=!1,u.height=g.fixedHeight+t.rect.element.paddingTop,s.overflow=_>u.height&&c?u.height:null;else if(g.cappedHeight){u.scalable=!0;var T=Math.min(g.cappedHeight,_);t.height=T+y,u.height=Math.min(g.cappedHeight+t.rect.element.paddingTop,I+y),s.overflow=_>u.height&&c?u.height:null}else u.scalable=!0,t.height=_+y+t.rect.element.paddingTop,u.height=I+y}},destroy:function(e){var t=e.root;t.ref.paster&&t.ref.paster.destroy(),t.ref.hopper&&t.ref.hopper.destroy()},mixins:{styles:["height"]}}),pn=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=null,n=ae(),l=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=o({},e),i=[],a=[],l=function(e,t,n){n?a.push({type:e,data:t}):(f[e]&&f[e](t),i.push({type:e,data:t}))},s=function(e){for(var t,n=arguments.length,r=Array(n>1?n-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:{};return new Promise(function(n,r){l.dispatch("ADD_ITEM",{interactionMethod:1,source:e,index:t.index,success:n,failure:r})})},g=function(e){return l.dispatch("REMOVE_ITEM",{query:e}),null===l.query("GET_ITEM",e)},_=function(){return l.query("GET_ITEMS")},I=function(e){return new Promise(function(t,n){l.dispatch("PROCESS_ITEM",{query:e,success:t,failure:n})})},y=o({},$(),p,function(e,t){var n={};return a(t,function(t){n[t]={get:function(){return e.getState().options[t]},set:function(n){e.dispatch("SET_"+z(t,"_").toUpperCase(),{value:n})}}}),n}(l,n),{setOptions:function(e){return l.dispatch("SET_OPTIONS",{options:e})},addFile:E,addFiles:function(){for(var e=arguments.length,t=Array(e),n=0;n0&&void 0!==arguments[0]?arguments[0]:{},t={};return a(ae(),function(e,n){t[e]=n[0]}),pn(o({},t,e))},mn=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=[].concat(i(e.attributes)).reduce(function(t,n){return t[function(e){return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"-";return e.replace(new RegExp(t+".","g"),function(e){return e.charAt(1).toUpperCase()})}(e.replace(/^data-/,""))}(n.name)]=u(e,n.name),t},{});return function e(t,n){a(n,function(n,r){a(t,function(e,o){var i=new RegExp(n);if(i.test(e)&&(delete t[e],!1!==r))if(x(r))t[r]=o;else{var a,l=r.group;V(r)&&!t[l]&&(t[l]={}),t[l][(a=e.replace(i,""),a.charAt(0).toLowerCase()+a.slice(1))]=o}}),r.mapping&&e(t[r.group],r.mapping)})}(n,t),n},vn=["fire","_read","_write"],hn=function(e){var t={};return Q(e,t,vn),t},En=function(e,t){return e.replace(/(?:{([a-z]+)})/g,function(e,n){return t[n]})},gn=["jpg","jpeg","png","gif","bmp","webp","svg","tiff"],_n=["css","csv","html","txt"],In={zip:"zip|compressed",epub:"application/epub+zip"},yn=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";return e=e.toLowerCase(),gn.includes(e)?"image/"+("jpg"===e?"jpeg":"svg"===e?"svg+xml":e):_n.includes(e)?"text/"+e:In[e]||null},Tn=function(e){var t=new Blob(["(",e.toString(),")()"],{type:"application/javascript"}),n=URL.createObjectURL(t),r=new Worker(n);return{transfer:function(e,t){},post:function(e,t,n){var o=j();r.onmessage=function(e){e.data.id===o&&t(e.data.message)},r.postMessage({id:o,message:e},n)},terminate:function(){r.terminate(),URL.revokeObjectURL(n)}}},bn=function(e,t){return new Promise(function(t,n){var r=new Image;r.onload=function(){t(r)},r.onerror=function(e){n(e)},r.src=e})},Rn=function(e){return _e(e,e.name)},wn=[],Dn=function(e){var t;wn.includes(e)||(wn.push(e),t=e({addFilter:ie,utils:{Type:te,forin:a,isString:x,toNaturalFileSize:ke,replaceInString:En,getExtensionFromFilename:me,getFilenameWithoutExtension:we,guesstimateMimeType:yn,getFileFromBlob:ve,getFilenameFromURL:de,createRoute:O,createWorker:Tn,createView:D,loadImage:bn,copyFile:Rn,renameFile:_e,applyFilterChain:re}}).options,Object.assign(le,t))},On={apps:[]},Sn="undefined"!=typeof navigator;if(Sn&&function(e){var t=1e3/(arguments.length>1&&void 0!==arguments[1]?arguments[1]:60),n=null;!function r(o){window.requestAnimationFrame(r),n||(n=o);var i=o-n;i<=t||(n=o-i%t,e(o))}(performance.now())}((Ke=On.apps,et="_read",tt="_write",function(e){Ke.forEach(function(e){return e[et]()}),Ke.forEach(function(t){return t[tt](e)})}),60),Sn){var An=function e(){document.dispatchEvent(new CustomEvent("FilePond:loaded",{detail:{supported:Bn,create:Cn,destroy:Nn,parse:xn,find:Gn,registerPlugin:kn,setOptions:Un}})),document.removeEventListener("DOMContentLoaded",e)};"loading"!==document.readyState?setTimeout(function(){return An()},0):document.addEventListener("DOMContentLoaded",An)}var Mn=function(){return a(ae(),function(e,t){Pn[e]=t[1]})},Ln=o({},De),Pn={};Mn();var Cn=function(){var e=function(){return(arguments.length<=0?void 0:arguments[0])instanceof HTMLElement?function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={"^class$":"className","^multiple$":"allowMultiple","^capture$":"captureMethod","^server":{group:"server",mapping:{"^process":{group:"process"},"^revert":{group:"revert"},"^fetch":{group:"fetch"},"^restore":{group:"restore"},"^load":{group:"load"}}},"^type$":!1,"^files$":!1};oe("SET_ATTRIBUTE_TO_OPTION_MAP",n);var r=o({},mn("FIELDSET"===e.nodeName?e.querySelector("input[type=file]"):e,n),t);r.files=(t.files||[]).concat([].concat(i(e.querySelectorAll("input:not([type=file])"))).map(function(e){return{source:e.value,options:{type:e.dataset.type}}}));var a=dn(r);return e.files&&[].concat(i(e.files)).forEach(function(e){a.addFile(e)}),a.replaceElement(e),a}.apply(void 0,arguments):dn.apply(void 0,arguments)}.apply(void 0,arguments);return e.on("destroy",Nn),On.apps.push(e),hn(e)},Nn=function(e){var t=On.apps.findIndex(function(t){return t.isAttachedTo(e)});return t>=0&&(On.apps.splice(t,1)[0].restoreElement(),!0)},xn=function(e){return[].concat(i(e.querySelectorAll(".filepond"))).filter(function(e){return!On.apps.find(function(t){return t.isAttachedTo(e)})}).map(function(e){return Cn(e)})},Gn=function(e){var t=On.apps.find(function(t){return t.isAttachedTo(e)});return t?hn(t):null},Bn=function(){return!!Sn&&!!("[object OperaMini]"!==Object.prototype.toString.call(window.operamini)&&"visibilityState"in document&&"Promise"in window&&"slice"in Blob.prototype&&"URL"in window&&"createObjectURL"in window.URL&&"performance"in window)},kn=function(){for(var e=arguments.length,t=Array(e),n=0;n=5&&l<=8){var f=[c,u];u=f[0],c=f[1]}var p=a.getMetadata("crop")||{rect:{x:0,y:0,width:1,height:1},aspectRatio:c/u},d=window.devicePixelRatio,m=n.query("GET_IMAGE_PREVIEW_HEIGHT"),v=n.query("GET_IMAGE_PREVIEW_MIN_HEIGHT"),h=n.query("GET_IMAGE_PREVIEW_MAX_HEIGHT"),E=n.rect.inner.width,g=c/u,_=E,I=E*g,y=null!==m?m:Math.max(v,Math.min(c,h)),T=y/g,b=function(e,n,r,o){if(n=Math.round(n),r=Math.round(r),o>=5&&o<=8){var i=[r,n];n=i[0],r=i[1]}var a=document.createElement("canvas"),l=a.getContext("2d");return o>=5&&o<=8?(a.width=r,a.height=n):(a.width=n,a.height=r),l.save(),t(l,n,r,o),l.drawImage(e,0,0,n,r),l.restore(),"close"in e&&e.close(),a}(o.data,T*d,y*d,l),R=null!==m?m:Math.max(v,Math.min(E*p.aspectRatio,h)),w=R/p.aspectRatio;w>_&&(R=(w=_)*p.aspectRatio);var D=R/(I*p.rect.height);u=_*D,c=I*D;var O=-p.rect.x*_*D,S=-p.rect.y*I*D;n.ref.clip.style.cssText="\n width: "+Math.round(w)+"px;\n height: "+Math.round(R)+"px;\n ",b.style.cssText="\n width: "+Math.round(u)+"px;\n height: "+Math.round(c)+"px;\n transform: translate("+Math.round(O)+"px, "+Math.round(S)+"px) rotateZ(0.00001deg);\n ",n.ref.clip.appendChild(b),n.dispatch("DID_IMAGE_PREVIEW_DRAW",{id:i})}}),mixins:{styles:["scaleX","scaleY","opacity"],animations:{scaleX:n,scaleY:n,opacity:{type:"tween",duration:750}}}})},o=function(){self.onmessage=function(t){e(t.data.message,function(e){self.postMessage({id:t.data.id,message:e},[e])})};var e=function(e,t){fetch(e.file).then(function(e){return e.blob()}).then(function(e){return createImageBitmap(e)}).then(function(e){return t(e)})}},i=function(e){return-.5*(Math.cos(Math.PI*e)-1)},a=function(e,t,n,r,o){e.width=t,e.height=n;var a=e.getContext("2d"),l=.5*t,s=a.createRadialGradient(l,n+110,n-100,l,n+110,n+100);!function(e,t){for(var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:i,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:10,a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0,l=1-a,s=t.join(","),u=0;u<=o;u++){var c=u/o,f=a+l*c;e.addColorStop(f,"rgba("+s+", "+r(c)*n+")")}}(s,r,o,void 0,8,.4),a.save(),a.translate(.5*-t,0),a.scale(2,1),a.fillStyle=s,a.fillRect(0,0,t,n),a.restore()},l="undefined"!=typeof navigator,s=l&&document.createElement("canvas"),u=l&&document.createElement("canvas"),c=l&&document.createElement("canvas");l&&(a(s,500,200,[40,40,40],.85),a(u,500,200,[196,78,71],1),a(c,500,200,[54,151,99],1));var f=function(e){var t=function(e){return e.utils.createView({name:"image-preview-overlay",tag:"canvas",ignoreRect:!0,create:function(e){var t,n,r=e.root;t=e.props.template,(n=r.element).width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0)},mixins:{styles:["opacity"],animations:{opacity:{type:"spring",mass:25}}}})}(e),n=function(e){var t=e.root;t.ref.overlayShadow.opacity=1,t.ref.overlayError.opacity=0,t.ref.overlaySuccess.opacity=0},i=function(e){var t=e.root;t.ref.overlayShadow.opacity=.25,t.ref.overlayError.opacity=1};return e.utils.createView({name:"image-preview-wrapper",create:function(n){var o=n.root,i=n.props,a=r(e);o.ref.image=o.appendChildView(o.createChildView(a,{id:i.id,scaleX:1.25,scaleY:1.25,opacity:0})),o.ref.overlayShadow=o.appendChildView(o.createChildView(t,{template:s,opacity:0})),o.ref.overlaySuccess=o.appendChildView(o.createChildView(t,{template:c,opacity:0})),o.ref.overlayError=o.appendChildView(o.createChildView(t,{template:u,opacity:0}))},write:e.utils.createRoute({DID_IMAGE_PREVIEW_LOAD:function(e){e.root.ref.overlayShadow.opacity=1},DID_IMAGE_PREVIEW_DRAW:function(e){var t=e.root.ref.image;t.scaleX=1,t.scaleY=1,t.opacity=1},DID_IMAGE_PREVIEW_CONTAINER_CREATE:function(t){var n,r,i,a=t.root,l=t.props,s=e.utils,u=(s.createView,s.createWorker),c=s.loadImage,f=l.id,p=a.query("GET_ITEM",f),d=URL.createObjectURL(p.file),m=function(e,t,n,r){c(d).then(v)},v=function(e){URL.revokeObjectURL(d),a.dispatch("DID_IMAGE_PREVIEW_LOAD",{id:f,data:e})};n=d,r=function(e,t){if(a.dispatch("DID_IMAGE_PREVIEW_CALCULATE_SIZE",{id:f,width:e,height:t}),"createImageBitmap"in window){var n=u(o);n.post({file:d},function(e){n.terminate(),e?v(e):m()})}else m()},(i=new Image).onload=function(){var e=i.naturalWidth,t=i.naturalHeight;i=null,r(e,t)},i.src=n},DID_THROW_ITEM_LOAD_ERROR:i,DID_THROW_ITEM_PROCESSING_ERROR:i,DID_THROW_ITEM_INVALID:i,DID_COMPLETE_ITEM_PROCESSING:function(e){var t=e.root;t.ref.overlayShadow.opacity=.25,t.ref.overlaySuccess.opacity=1},DID_START_ITEM_PROCESSING:n,DID_REVERT_ITEM_PROCESSING:n})})},p=function(e){var t=e.addFilter,n=e.utils,r=n.Type,o=n.createRoute,i=f(e);return t("CREATE_VIEW",function(e){var t=e.is,n=e.view,r=e.query;t("file")&&r("GET_ALLOW_IMAGE_PREVIEW")&&n.registerWriter(o({DID_LOAD_ITEM:function(e){var t=e.root,o=e.props.id,a=r("GET_ITEM",o);if(a){var l=a.file;if(function(e){return/^image/.test(e.type)&&!/svg/.test(e.type)}(l)){var s="createImageBitmap"in(window||{}),u=r("GET_IMAGE_PREVIEW_MAX_FILE_SIZE");!s&&u&&l.size>u||(t.ref.imagePreview=n.appendChildView(n.createChildView(i,{id:o})),t.dispatch("DID_IMAGE_PREVIEW_CONTAINER_CREATE",{id:o}))}}},DID_IMAGE_PREVIEW_CALCULATE_SIZE:function(e){var t=e.root,n=e.props,r=e.action,o=t.query("GET_ITEM",{id:n.id}),i=(o.getMetadata("exif")||{}).orientation||-1,a=r.width,l=r.height;if(i>=5&&i<=8){var s=[l,a];a=s[0],l=s[1]}var u=o.getMetadata("crop")||{rect:{x:0,y:0,width:1,height:1},aspectRatio:l/a},c=t.query("GET_IMAGE_PREVIEW_HEIGHT"),f=t.query("GET_IMAGE_PREVIEW_MIN_HEIGHT"),p=t.query("GET_IMAGE_PREVIEW_MAX_HEIGHT");(a=(l=null!==c?c:Math.max(f,Math.min(l,p)))/u.aspectRatio)>t.rect.element.width&&(l=(a=t.rect.element.width)*u.aspectRatio),t.ref.imagePreview.element.style.cssText="height:"+Math.round(l)+"px"}}))}),{options:{allowImagePreview:[!0,r.BOOLEAN],imagePreviewHeight:[null,r.INT],imagePreviewMinHeight:[44,r.INT],imagePreviewMaxHeight:[256,r.INT],imagePreviewMaxFileSize:[null,r.INT]}}};return"undefined"!=typeof navigator&&document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:p})),p},"object"===("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)&&void 0!==e?e.exports=i():void 0===(o="function"==typeof(r=i)?r.call(t,n,t,e):r)||(e.exports=o)},3:function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n,r=e[1]||"",o=e[3];if(!o)return r;if(t&&"function"==typeof btoa){var i=(n=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),a=o.sources.map(function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"});return[r].concat(a).concat([i]).join("\n")}return[r].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;o=0&&c.splice(t,1)}function h(e){var t=document.createElement("style");return e.attrs.type="text/css",E(t,e.attrs),m(e,t),t}function E(e,t){Object.keys(t).forEach(function(n){e.setAttribute(n,t[n])})}function g(e,t){var n,r,o,i;if(t.transform&&e.css){if(!(i=t.transform(e.css)))return function(){};e.css=i}if(t.singleton){var a=u++;n=s||(s=h(t)),r=y.bind(null,n,a,!1),o=y.bind(null,n,a,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",E(t,e.attrs),m(e,t),t}(t),r=function(e,t,n){var r=n.css,o=n.sourceMap,i=void 0===t.convertToAbsoluteUrls&&o;(t.convertToAbsoluteUrls||i)&&(r=f(r)),o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var a=new Blob([r],{type:"text/css"}),l=e.href;e.href=URL.createObjectURL(a),l&&URL.revokeObjectURL(l)}.bind(null,n,t),o=function(){v(n),n.href&&URL.revokeObjectURL(n.href)}):(n=h(t),r=function(e,t){var n=t.css,r=t.media;if(r&&e.setAttribute("media",r),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){v(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=a()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=d(e,t);return p(n,t),function(e){for(var r=[],o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o0&&this.addIndent(a,o,l+c),e.preventDefault()}"}"!==e.key&&"]"!==e.key||this.removeIndent(a,o)},getPreviousLine:function(e,t){return e.split(/\n/g)[t.trimRight().split(/\n/g).length-1]||""},getIndents:function(e){var t=this.options.indent,r=new RegExp("^("+t+"+)","g"),n=e.match(r);return n&&n[0].length/t.length||0},addIndent:function(e,t,r){if(r){var n=this.$refs.textarea,a=e+"\n"+this.options.indent.repeat(r)+t;this.processedValue=a,this.lastValue=a;var o=a.length-t.length;n.selectionStart=o,n.selectionEnd=o}},removeIndent:function(e,t){var r=this.$refs.textarea,n=this.options.indent;if(e.slice(e.length-n.length,e.length)===n){var a=e.slice(0,-n.length)+t,o=e.length-n.length;this.processedValue=a,this.lastValue=a,r.selectionStart=o,r.selectionEnd=o}}}},o=r(0),i=Object(o.a)(a,function(){var e=this,t=e.$createElement;return(e._self._c||t)("textarea",{ref:"textarea",attrs:{readonly:e.readonly,placeholder:e.options.placeholder,rows:"10"},domProps:{value:e.processedValue},on:{keydown:e.process,input:function(t){e.updateValue(t.target.value)}}})},[],!1,function(e){r(134)},"data-v-46ef3cba",null);t.default=i.exports},4:function(e,t,r){"use strict";r.r(t),r.d(t,"default",function(){return v});var n=r(2),a=r.n(n),o="undefined"!=typeof document;if("undefined"!=typeof DEBUG&&DEBUG&&!o)throw new Error("vue-style-loader cannot be used in a non-browser environment. Use { target: 'node' } in your Webpack config to indicate a server-rendering environment.");var i={},s=o&&(document.head||document.getElementsByTagName("head")[0]),u=null,l=0,c=!1,d=function(){},f=null,p="data-vue-ssr-id",h="undefined"!=typeof navigator&&/msie [6-9]\b/.test(navigator.userAgent.toLowerCase());function v(e,t,r,n){c=r,f=n||{};var o=a()(e,t);return g(o),function(t){for(var r=[],n=0;nr.parts.length&&(n.parts.length=r.parts.length)}else{var o=[];for(a=0;a[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:b,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};function o(e){this.tokens=[],this.tokens.links={},this.options=e||y.defaults,this.rules=r.normal,this.options.gfm&&(this.options.tables?this.rules=r.tables:this.rules=r.gfm)}r.bullet=/(?:[*+-]|\d+\.)/,r.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,r.item=c(r.item,"gm")(/bull/g,r.bullet)(),r.list=c(r.list)(/bull/g,r.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+r.def.source+")")(),r.blockquote=c(r.blockquote)("def",r.def)(),r._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",r.html=c(r.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,r._tag)(),r.paragraph=c(r.paragraph)("hr",r.hr)("heading",r.heading)("lheading",r.lheading)("blockquote",r.blockquote)("tag","<"+r._tag)("def",r.def)(),r.normal=m({},r),r.gfm=m({},r.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),r.gfm.paragraph=c(r.paragraph)("(?!","(?!"+r.gfm.fences.source.replace("\\1","\\2")+"|"+r.list.source.replace("\\1","\\3")+"|")(),r.tables=m({},r.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),o.rules=r,o.lex=function(e,t){return new o(t).lex(e)},o.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},o.prototype.token=function(e,t,n){var i,s,o,l,a,u,h,p,c;for(e=e.replace(/^ +$/gm,"");e;)if((o=this.rules.newline.exec(e))&&(e=e.substring(o[0].length),o[0].length>1&&this.tokens.push({type:"space"})),o=this.rules.code.exec(e))e=e.substring(o[0].length),o=o[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?o:o.replace(/\n+$/,"")});else if(o=this.rules.fences.exec(e))e=e.substring(o[0].length),this.tokens.push({type:"code",lang:o[2],text:o[3]||""});else if(o=this.rules.heading.exec(e))e=e.substring(o[0].length),this.tokens.push({type:"heading",depth:o[1].length,text:o[2]});else if(t&&(o=this.rules.nptable.exec(e))){for(e=e.substring(o[0].length),u={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/\n$/,"").split("\n")},p=0;p ?/gm,""),this.token(o,t,!0),this.tokens.push({type:"blockquote_end"});else if(o=this.rules.list.exec(e)){for(e=e.substring(o[0].length),l=o[2],this.tokens.push({type:"list_start",ordered:l.length>1}),i=!1,c=(o=o[0].match(this.rules.item)).length,p=0;p1&&a.length>1||(e=o.slice(p+1).join("\n")+e,p=c-1)),s=i||/\n\n(?!\s*$)/.test(u),p!==c-1&&(i="\n"===u.charAt(u.length-1),s||(s=i)),this.tokens.push({type:s?"loose_item_start":"list_item_start"}),this.token(u,!1,n),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(o=this.rules.html.exec(e))e=e.substring(o[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&("pre"===o[1]||"script"===o[1]||"style"===o[1]),text:o[0]});else if(!n&&t&&(o=this.rules.def.exec(e)))e=e.substring(o[0].length),this.tokens.links[o[1].toLowerCase()]={href:o[2],title:o[3]};else if(t&&(o=this.rules.table.exec(e))){for(e=e.substring(o[0].length),u={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/(?: *\| *)?\n$/,"").split("\n")},p=0;p])/,autolink:/^<([^ <>]+(@|:\/)[^ <>]+)>/,url:b,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^<'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)([\s\S]*?[^`])\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:b,text:/^[\s\S]+?(?=[\\/g,">").replace(/"/g,""").replace(/'/g,"'")}function c(e,t){return e=e.source,t=t||"",function n(r,i){return r?(i=(i=i.source||i).replace(/(^|[^\[])\^/g,"$1"),e=e.replace(r,i),n):new RegExp(e,t)}}function f(e,t){return d[" "+e]||(/^[^:]+:\/*[^/]*$/.test(e)?d[" "+e]=e+"/":d[" "+e]=e.replace(/[^/]*$/,"")),e=d[" "+e],"//"===t.slice(0,2)?e.replace(/:[\s\S]*/,":")+t:"/"===t.charAt(0)?e.replace(/(:\/*[^/]*)[\s\S]*/,"$1")+t:e+t}l._inside=/(?:\[[^\]]*\]|\\[\[\]]|[^\[\]]|\](?=[^\[]*\]))*/,l._href=/\s*?(?:\s+['"]([\s\S]*?)['"])?\s*/,l.link=c(l.link)("inside",l._inside)("href",l._href)(),l.reflink=c(l.reflink)("inside",l._inside)(),l.normal=m({},l),l.pedantic=m({},l.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),l.gfm=m({},l.normal,{escape:c(l.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:c(l.text)("]|","~]|")("|","|https?://|")()}),l.breaks=m({},l.gfm,{br:c(l.br)("{2,}","*")(),text:c(l.gfm.text)("{2,}","*")()}),a.rules=l,a.output=function(e,t,n){return new a(t,n).output(e)},a.prototype.output=function(e){for(var t,n,r,i,s="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),s+=i[1];else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),"@"===i[2]?(n=p(":"===i[1].charAt(6)?this.mangle(i[1].substring(7)):this.mangle(i[1])),r=this.mangle("mailto:")+n):r=n=p(i[1]),s+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.tag.exec(e))!this.inLink&&/^/i.test(i[0])&&(this.inLink=!1),e=e.substring(i[0].length),s+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):p(i[0]):i[0];else if(i=this.rules.link.exec(e))e=e.substring(i[0].length),this.inLink=!0,s+=this.outputLink(i,{href:i[2],title:i[3]}),this.inLink=!1;else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){s+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,s+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),s+=this.renderer.strong(this.output(i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),s+=this.renderer.em(this.output(i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),s+=this.renderer.codespan(p(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),s+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),s+=this.renderer.del(this.output(i[1]));else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),s+=this.renderer.text(p(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else e=e.substring(i[0].length),r=n=p(i[1]),s+=this.renderer.link(r,null,n);return s},a.prototype.outputLink=function(e,t){var n=p(t.href),r=t.title?p(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,p(e[1]))},a.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},a.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,i=0;i.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n},u.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'
'+(n?e:p(e,!0))+"\n
\n":"
"+(n?e:p(e,!0))+"\n
"},u.prototype.blockquote=function(e){return"
\n"+e+"
\n"},u.prototype.html=function(e){return e},u.prototype.heading=function(e,t,n){return"'+e+"\n"},u.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},u.prototype.list=function(e,t){var n=t?"ol":"ul";return"<"+n+">\n"+e+"\n"},u.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},u.prototype.paragraph=function(e){return"

    "+e+"

    \n"},u.prototype.table=function(e,t){return"\n\n"+e+"\n\n"+t+"\n
    \n"},u.prototype.tablerow=function(e){return"\n"+e+"\n"},u.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' style="text-align:'+t.align+'">':"<"+n+">")+e+"\n"},u.prototype.strong=function(e){return""+e+""},u.prototype.em=function(e){return""+e+""},u.prototype.codespan=function(e){return""+e+""},u.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},u.prototype.del=function(e){return""+e+""},u.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent((i=e,i.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""}))).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return n}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:")||0===r.indexOf("data:"))return n}var i;this.options.baseUrl&&!g.test(e)&&(e=f(this.options.baseUrl,e));var s='
    "+n+""},u.prototype.image=function(e,t,n){this.options.baseUrl&&!g.test(e)&&(e=f(this.options.baseUrl,e));var r=''+n+'":">")},u.prototype.text=function(e){return e},h.parse=function(e,t,n){return new h(t,n).parse(e)},h.prototype.parse=function(e){this.inline=new a(e.links,this.options,this.renderer),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},h.prototype.next=function(){return this.token=this.tokens.pop()},h.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},h.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},h.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,i="",s="";for(n="",e=0;eAn error occurred:

    "+p(e.message+"",!0)+"
    ";throw e}}b.exec=b,y.options=y.setOptions=function(e){return m(y.defaults,e),y},y.defaults={gfm:!0,tables:!0,breaks:!1,pedantic:!1,sanitize:!1,sanitizer:null,mangle:!0,smartLists:!1,silent:!1,highlight:null,langPrefix:"lang-",smartypants:!1,headerPrefix:"",renderer:new u,xhtml:!1,baseUrl:null},y.Parser=h,y.parser=h.parse,y.Renderer=u,y.Lexer=o,y.lexer=o.lex,y.InlineLexer=a,y.inlineLexer=a.output,y.parse=y,void 0!==e&&"object"===s(t)?e.exports=y:void 0===(i=function(){return y}.call(t,n,t,e))||(e.exports=i)}).call(function(){return this||("undefined"!=typeof window?window:r)}())}).call(this,n(14))},3:function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n,r=e[1]||"",i=e[3];if(!i)return r;if(t&&"function"==typeof btoa){var s=(n=i,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),o=i.sources.map(function(e){return"/*# sourceURL="+i.sourceRoot+e+" */"});return[r].concat(o).concat([s]).join("\n")}return[r].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},i=0;in.parts.length&&(r.parts.length=n.parts.length)}else{var s=[];for(i=0;in.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o":"greater","|":"or","¢":"cent","£":"pound","¤":"currency","¥":"yen","©":"(c)","ª":"a","®":"(r)","º":"o","À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","Æ":"AE","Ç":"C","È":"E","É":"E","Ê":"E","Ë":"E","Ì":"I","Í":"I","Î":"I","Ï":"I","Ð":"D","Ñ":"N","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","Ù":"U","Ú":"U","Û":"U","Ü":"U","Ý":"Y","Þ":"TH","ß":"ss","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","æ":"ae","ç":"c","è":"e","é":"e","ê":"e","ë":"e","ì":"i","í":"i","î":"i","ï":"i","ð":"d","ñ":"n","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","ù":"u","ú":"u","û":"u","ü":"u","ý":"y","þ":"th","ÿ":"y","Ā":"A","ā":"a","Ă":"A","ă":"a","Ą":"A","ą":"a","Ć":"C","ć":"c","Č":"C","č":"c","Ď":"D","ď":"d","Đ":"DJ","đ":"dj","Ē":"E","ē":"e","Ė":"E","ė":"e","Ę":"e","ę":"e","Ě":"E","ě":"e","Ğ":"G","ğ":"g","Ģ":"G","ģ":"g","Ĩ":"I","ĩ":"i","Ī":"i","ī":"i","Į":"I","į":"i","İ":"I","ı":"i","Ķ":"k","ķ":"k","Ļ":"L","ļ":"l","Ł":"L","ł":"l","Ń":"N","ń":"n","Ņ":"N","ņ":"n","Ň":"N","ň":"n","Ő":"O","ő":"o","Œ":"OE","œ":"oe","Ř":"R","ř":"r","Ś":"S","ś":"s","Ş":"S","ş":"s","Š":"S","š":"s","Ţ":"T","ţ":"t","Ť":"T","ť":"t","Ũ":"U","ũ":"u","Ū":"u","ū":"u","Ů":"U","ů":"u","Ű":"U","ű":"u","Ų":"U","ų":"u","Ź":"Z","ź":"z","Ż":"Z","ż":"z","Ž":"Z","ž":"z","ƒ":"f","Ơ":"O","ơ":"o","Ư":"U","ư":"u","Lj":"LJ","lj":"lj","Nj":"NJ","nj":"nj","Ș":"S","ș":"s","Ț":"T","ț":"t","˚":"o","Ά":"A","Έ":"E","Ή":"H","Ί":"I","Ό":"O","Ύ":"Y","Ώ":"W","ΐ":"i","Α":"A","Β":"B","Γ":"G","Δ":"D","Ε":"E","Ζ":"Z","Η":"H","Θ":"8","Ι":"I","Κ":"K","Λ":"L","Μ":"M","Ν":"N","Ξ":"3","Ο":"O","Π":"P","Ρ":"R","Σ":"S","Τ":"T","Υ":"Y","Φ":"F","Χ":"X","Ψ":"PS","Ω":"W","Ϊ":"I","Ϋ":"Y","ά":"a","έ":"e","ή":"h","ί":"i","ΰ":"y","α":"a","β":"b","γ":"g","δ":"d","ε":"e","ζ":"z","η":"h","θ":"8","ι":"i","κ":"k","λ":"l","μ":"m","ν":"n","ξ":"3","ο":"o","π":"p","ρ":"r","ς":"s","σ":"s","τ":"t","υ":"y","φ":"f","χ":"x","ψ":"ps","ω":"w","ϊ":"i","ϋ":"y","ό":"o","ύ":"y","ώ":"w","Ё":"Yo","Ђ":"DJ","Є":"Ye","І":"I","Ї":"Yi","Ј":"J","Љ":"LJ","Њ":"NJ","Ћ":"C","Џ":"DZ","А":"A","Б":"B","В":"V","Г":"G","Д":"D","Е":"E","Ж":"Zh","З":"Z","И":"I","Й":"J","К":"K","Л":"L","М":"M","Н":"N","О":"O","П":"P","Р":"R","С":"S","Т":"T","У":"U","Ф":"F","Х":"H","Ц":"C","Ч":"Ch","Ш":"Sh","Щ":"Sh","Ъ":"U","Ы":"Y","Ь":"","Э":"E","Ю":"Yu","Я":"Ya","а":"a","б":"b","в":"v","г":"g","д":"d","е":"e","ж":"zh","з":"z","и":"i","й":"j","к":"k","л":"l","м":"m","н":"n","о":"o","п":"p","р":"r","с":"s","т":"t","у":"u","ф":"f","х":"h","ц":"c","ч":"ch","ш":"sh","щ":"sh","ъ":"u","ы":"y","ь":"","э":"e","ю":"yu","я":"ya","ё":"yo","ђ":"dj","є":"ye","і":"i","ї":"yi","ј":"j","љ":"lj","њ":"nj","ћ":"c","џ":"dz","Ґ":"G","ґ":"g","฿":"baht","ა":"a","ბ":"b","გ":"g","დ":"d","ე":"e","ვ":"v","ზ":"z","თ":"t","ი":"i","კ":"k","ლ":"l","მ":"m","ნ":"n","ო":"o","პ":"p","ჟ":"zh","რ":"r","ს":"s","ტ":"t","უ":"u","ფ":"f","ქ":"k","ღ":"gh","ყ":"q","შ":"sh","ჩ":"ch","ც":"ts","ძ":"dz","წ":"ts","ჭ":"ch","ხ":"kh","ჯ":"j","ჰ":"h","ẞ":"SS","Ạ":"A","ạ":"a","Ả":"A","ả":"a","Ấ":"A","ấ":"a","Ầ":"A","ầ":"a","Ẩ":"A","ẩ":"a","Ẫ":"A","ẫ":"a","Ậ":"A","ậ":"a","Ắ":"A","ắ":"a","Ằ":"A","ằ":"a","Ẳ":"A","ẳ":"a","Ẵ":"A","ẵ":"a","Ặ":"A","ặ":"a","Ẹ":"E","ẹ":"e","Ẻ":"E","ẻ":"e","Ẽ":"E","ẽ":"e","Ế":"E","ế":"e","Ề":"E","ề":"e","Ể":"E","ể":"e","Ễ":"E","ễ":"e","Ệ":"E","ệ":"e","Ỉ":"I","ỉ":"i","Ị":"I","ị":"i","Ọ":"O","ọ":"o","Ỏ":"O","ỏ":"o","Ố":"O","ố":"o","Ồ":"O","ồ":"o","Ổ":"O","ổ":"o","Ỗ":"O","ỗ":"o","Ộ":"O","ộ":"o","Ớ":"O","ớ":"o","Ờ":"O","ờ":"o","Ở":"O","ở":"o","Ỡ":"O","ỡ":"o","Ợ":"O","ợ":"o","Ụ":"U","ụ":"u","Ủ":"U","ủ":"u","Ứ":"U","ứ":"u","Ừ":"U","ừ":"u","Ử":"U","ử":"u","Ữ":"U","ữ":"u","Ự":"U","ự":"u","Ỳ":"Y","ỳ":"y","Ỵ":"Y","ỵ":"y","Ỷ":"Y","ỷ":"y","Ỹ":"Y","ỹ":"y","‘":"\'","’":"\'","“":"\\"","”":"\\"","†":"+","•":"*","…":"...","₠":"ecu","₢":"cruzeiro","₣":"french franc","₤":"lira","₥":"mill","₦":"naira","₧":"peseta","₨":"rupee","₩":"won","₪":"new shequel","₫":"dong","€":"euro","₭":"kip","₮":"tugrik","₯":"drachma","₰":"penny","₱":"peso","₲":"guarani","₳":"austral","₴":"hryvnia","₵":"cedi","₹":"indian rupee","₽":"russian ruble","℠":"sm","™":"tm","∂":"d","∆":"delta","∑":"sum","∞":"infinity","♥":"love","元":"yuan","円":"yen","﷼":"rial"}');function t(t,n){return n="string"==typeof n?{replacement:n}:n||{},t=t.split("").reduce(function(t,r){return e[r]&&(r=e[r]),t+r.replace(n.remove||/[^\w\s$*_+~.()'"!\-:@]/g,"")},"").replace(/^\s+|\s+$/g,"").replace(/[-\s]+/g,n.replacement||"-").replace("#{replacement}$",""),n.lower?t.toLowerCase():t}return t.extend=function(t){for(var n in t)e[n]=t[n]},t},"object"===("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)?(e.exports=i(),e.exports.default=i()):void 0===(o="function"==typeof(r=i)?r.call(t,n,t,e):r)||(e.exports=o)},3:function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n,r=e[1]||"",o=e[3];if(!o)return r;if(t&&"function"==typeof btoa){var i=(n=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),a=o.sources.map(function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"});return[r].concat(a).concat([i]).join("\n")}return[r].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o7&&e<=25?"small":"medium":"normal"},mirror:function(){var e=this.options.mirroredField;return this.$store.state.edits.values&&this.$store.state.edits.values[e]||""}},watch:{mirror:function(){this.updateValue(this.mirror)}},methods:{updateValue:function(e){this.$emit("input",o()(e,{lower:this.options.forceLowercase}))}}},s=n(0),u=Object(s.a)(a,function(){var e=this.$createElement;return(this._self._c||e)("v-input",{class:this.width,attrs:{type:"text",value:this.value,readonly:this.readonly,placeholder:this.options.placeholder,maxlength:this.length,id:this.name},on:{input:this.updateValue}})},[],!1,function(e){n(122)},"data-v-48408d90",null);t.default=u.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/slug/Readonly.js b/public/extensions/core/interfaces/slug/Readonly.js new file mode 100644 index 0000000000..595562009b --- /dev/null +++ b/public/extensions/core/interfaces/slug/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=60)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,u,s){var l=typeof(e=e||{}).default;"object"!==l&&"function"!==l||(e=e.default);var a,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),u?(a=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(u)},c._ssrRegister=a):o&&(a=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),a)if(c.functional){c._injectStyles=a;var f=c.render;c.render=function(e,t){return a.call(t),f(e,t)}}else{var p=c.beforeCreate;c.beforeCreate=p?[].concat(p,a):[a]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},60:function(e,t,n){"use strict";n.r(t);var r=n(1),o={mixins:[n.n(r).a]},i=n(0),u=Object(i.a)(o,function(){var e=this.$createElement;return(this._self._c||e)("span",[this._v(this._s(this.value))])},[],!1,null,null,null);t.default=u.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/slug/meta.json b/public/extensions/core/interfaces/slug/meta.json new file mode 100644 index 0000000000..f34138a7d2 --- /dev/null +++ b/public/extensions/core/interfaces/slug/meta.json @@ -0,0 +1,57 @@ +{ + "name": "$t:slug", + "version": "1.0.0", + "datatypes": { + "VARCHAR": 100 + }, + "options": { + "placeholder": { + "name": "$t:placeholder_name", + "comment": "$t:placeholder_comment", + "interface": "text-input", + "length": 200 + }, + "forceLowercase": { + "name": "$t:force_lowercase", + "comment": "$t:force_lowercase_comment", + "interface": "toggle", + "default": true + }, + "mirroredField": { + "name": "$t:mirrored_field", + "comment": "$t:mirrored_field_comment", + "interface": "text-input" + }, + "width": { + "name": "$t:width", + "comment": "$t:width_comment", + "interface": "dropdown", + "default": "auto", + "options": { + "choices": { + "auto": "$t:auto", + "small": "$t:small", + "medium": "$t:medium", + "large": "$t:large" + } + } + } + }, + "translation": { + "en-US": { + "slug": "Slug", + "placeholder_name": "Placeholder", + "placeholder_comment": "The placeholder text to show", + "force_lowercase": "Force Lowercase", + "force_lowercase_comment": "Makes sure the slug is in lowercase", + "mirrored_field": "Mirrored Field", + "mirrored_field_comment": "Keep the slug up to date with another (text) field", + "width": "Size", + "width_comment": "Set what width to use for the input", + "auto": "Automatic", + "small": "Small", + "medium": "Medium", + "large": "Large" + } + } +} diff --git a/public/extensions/core/interfaces/sort/Interface.js b/public/extensions/core/interfaces/sort/Interface.js new file mode 100644 index 0000000000..d165447483 --- /dev/null +++ b/public/extensions/core/interfaces/sort/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=61)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,u,i,a){var l=typeof(e=e||{}).default;"object"!==l&&"function"!==l||(e=e.default);var s,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),u&&(c._scopeId=u),i?(s=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(i)},c._ssrRegister=s):o&&(s=a?function(){o.call(this,this.$root.$options.shadowRoot)}:o),s)if(c.functional){c._injectStyles=s;var f=c.render;c.render=function(e,t){return s.call(t),f(e,t)}}else{var p=c.beforeCreate;c.beforeCreate=p?[].concat(p,s):[s]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},12:function(e){e.exports={}},61:function(e,t,n){"use strict";n.r(t),n(12);var r=n(1),o={mixins:[n.n(r).a],methods:{updateValue:function(e){this.$emit("input",Number(e))}}},u=n(0),i=Object(u.a)(o,function(){var e=this,t=e.$createElement;return(e._self._c||t)("input",{attrs:{type:"number",readonly:e.readonly,min:"0"},domProps:{value:e.value},on:{input:function(t){e.updateValue(t.target.value)}}})},[],!1,null,null,null);t.default=i.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/sort/Readonly.js b/public/extensions/core/interfaces/sort/Readonly.js new file mode 100644 index 0000000000..bdf0ef7c20 --- /dev/null +++ b/public/extensions/core/interfaces/sort/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=63)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,u,s){var l=typeof(e=e||{}).default;"object"!==l&&"function"!==l||(e=e.default);var a,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),u?(a=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(u)},c._ssrRegister=a):o&&(a=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),a)if(c.functional){c._injectStyles=a;var f=c.render;c.render=function(e,t){return a.call(t),f(e,t)}}else{var p=c.beforeCreate;c.beforeCreate=p?[].concat(p,a):[a]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},12:function(e){e.exports={}},63:function(e,t,n){"use strict";n.r(t),n(12);var r=n(1),o={mixins:[n.n(r).a]},i=n(0),u=Object(i.a)(o,function(){var e=this.$createElement;return(this._self._c||e)("span",[this._v(this._s(this.value))])},[],!1,null,null,null);t.default=u.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/sort/meta.json b/public/extensions/core/interfaces/sort/meta.json new file mode 100644 index 0000000000..a6e3c3e192 --- /dev/null +++ b/public/extensions/core/interfaces/sort/meta.json @@ -0,0 +1,17 @@ +{ + "name": "$t:sort", + "version": "1.0.0", + "datatypes": { + "INT": 11, + "TINYINT": 1, + "SMALLINT": 5, + "MEDIUMINT": 7, + "BIGINT": 18 + }, + "options": {}, + "translation": { + "en-US": { + "sort": "Sort" + } + } +} diff --git a/public/extensions/core/interfaces/status/Interface.js b/public/extensions/core/interfaces/status/Interface.js new file mode 100644 index 0000000000..19dc4e1d6e --- /dev/null +++ b/public/extensions/core/interfaces/status/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=64)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,a,s){var u=typeof(e=e||{}).default;"object"!==u&&"function"!==u||(e=e.default);var c,l="function"==typeof e?e.options:e;if(t&&(l.render=t,l.staticRenderFns=n,l._compiled=!0),r&&(l.functional=!0),i&&(l._scopeId=i),a?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(a)},l._ssrRegister=c):o&&(c=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(l.functional){l._injectStyles=c;var f=l.render;l.render=function(e,t){return c.call(t),f(e,t)}}else{var p=l.beforeCreate;l.beforeCreate=p?[].concat(p,c):[c]}return{exports:e,options:l}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},119:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".interface-status[data-v-3c9c386a]{display:grid;grid-auto-flow:column;grid-auto-columns:max-content;grid-gap:20px;padding:20px 0}",""])},120:function(e,t,n){var r=n(119);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("f0607f52",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(o=0;o0&&t.push(e),t=[].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);tn.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o7&&e<=25?"small":"medium":"normal"}},methods:{updateValue:function(e){var t=e;this.options.trim&&(t=t.trim()),this.$emit("input",t)}}},i=n(0),a=Object(i.a)(o,function(){var e=this,t=e.$createElement;return(e._self._c||t)("v-input",{class:e.width,attrs:{type:"text",value:e.value||"",readonly:e.readonly,placeholder:e.options.placeholder,maxlength:+e.length,id:e.name,charactercount:e.options.showCharacterCount},on:{input:e.updateValue}})},[],!1,function(e){n(111)},"data-v-0c4e33c7",null);t.default=a.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/text-input/Readonly.js b/public/extensions/core/interfaces/text-input/Readonly.js new file mode 100644 index 0000000000..29ddee3056 --- /dev/null +++ b/public/extensions/core/interfaces/text-input/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=72)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,u,l){var s=typeof(e=e||{}).default;"object"!==s&&"function"!==s||(e=e.default);var a,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),u?(a=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(u)},c._ssrRegister=a):o&&(a=l?function(){o.call(this,this.$root.$options.shadowRoot)}:o),a)if(c.functional){c._injectStyles=a;var f=c.render;c.render=function(e,t){return a.call(t),f(e,t)}}else{var p=c.beforeCreate;c.beforeCreate=p?[].concat(p,a):[a]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},72:function(e,t,n){"use strict";n.r(t);var r=n(1),o={mixins:[n.n(r).a],computed:{displayValue:function(){var e=this.value;return this.options.formatValue&&(e=this.$helpers.formatTitle(e)),e}}},i=n(0),u=Object(i.a)(o,function(){var e=this.$createElement;return(this._self._c||e)("div",[this._v(this._s(this.displayValue))])},[],!1,null,null,null);t.default=u.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/text-input/meta.json b/public/extensions/core/interfaces/text-input/meta.json new file mode 100644 index 0000000000..b94e20c3b5 --- /dev/null +++ b/public/extensions/core/interfaces/text-input/meta.json @@ -0,0 +1,67 @@ +{ + "name": "$t:input", + "version": "1.0.0", + "datatypes": { + "VARCHAR": 100, + "CHAR": 1 + }, + "options": { + "placeholder": { + "name": "$t:placeholder", + "comment": "$t:placeholder_comment", + "interface": "text-input", + "length": 200 + }, + "trim": { + "name": "$t:trim", + "comment": "$t:trim_comment", + "interface": "toggle", + "default": true + }, + "showCharacterCount": { + "name": "$t:char_count", + "comment": "$t:char_count_comment", + "interface": "toggle", + "default": true + }, + "formatValue": { + "name": "$t:format", + "comment": "$t:format_comment", + "interface": "toggle", + "default": false + }, + "width": { + "name": "$t:width", + "comment": "$t:width_comment", + "interface": "dropdown", + "default": "auto", + "options": { + "choices": { + "auto": "$t:auto", + "small": "$t:small", + "medium": "$t:medium", + "large": "$t:large" + } + } + } + }, + "translation": { + "en-US": { + "input": "Text Input", + "placeholder": "Placeholder", + "placeholder_comment": "Enter placeholder text", + "trim": "Trim", + "trim_comment": "Trim surrounding whitespace from the value before saving", + "char_count": "Show Character Count", + "char_count_comment": "Show the remaining characters available next to the input", + "format": "Pretty Output", + "format_comment": "Convert the value to title case", + "width": "Size", + "width_comment": "Set what width to use for the input", + "auto": "Automatic", + "small": "Small", + "medium": "Medium", + "large": "Large" + } + } +} diff --git a/public/extensions/core/interfaces/textarea/Interface.js b/public/extensions/core/interfaces/textarea/Interface.js new file mode 100644 index 0000000000..6e6ddf9438 --- /dev/null +++ b/public/extensions/core/interfaces/textarea/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=73)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,a,i,s){var u=typeof(e=e||{}).default;"object"!==u&&"function"!==u||(e=e.default);var l,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),a&&(c._scopeId=a),i?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(i)},c._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(c.functional){c._injectStyles=l;var d=c.render;c.render=function(e,t){return l.call(t),d(e,t)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},108:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,".textarea[data-v-d5bf89de]{max-width:var(--width-large)}",""])},109:function(e,t,n){var r=n(108);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("942d3b68",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o=12&&(o="PM"),t="00"==(t=t>12?t-12:t)?12:t,r?t+":"+n+":"+r+" "+o:t+":"+n+" "+o}return this.value}}},i=n(0),u=Object(i.a)(o,function(){var e=this.$createElement,t=this._self._c||e;return this.options.showRelative?t("v-timeago",{attrs:{since:this.date,"auto-update":this.options.includeSeconds?1:60,locale:this.$i18n.locale}}):t("span",[this._v(this._s(this.displayValue))])},[],!1,null,null,null);t.default=u.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/time/meta.json b/public/extensions/core/interfaces/time/meta.json new file mode 100644 index 0000000000..eb882253f8 --- /dev/null +++ b/public/extensions/core/interfaces/time/meta.json @@ -0,0 +1,24 @@ +{ + "name": "$t:time", + "version": "1.0.0", + "datatypes": { + "TIME": null + }, + "options": { + "display24HourClock": { + "name": "$t:24hour", + "comment": "$t:24hour_comment", + "interface": "toggle", + "default": true + } + }, + "translation": { + "en-US": { + "time": "Time", + "include_seconds": "Include seconds", + "include_seconds_comment": "Include seconds in the interface", + "24hour": "Display 24 hour clock", + "24hour_comment": "Show the time in 24-hour format (eg.: 15:30)" + } + } +} diff --git a/public/extensions/core/interfaces/toggle/Interface.js b/public/extensions/core/interfaces/toggle/Interface.js new file mode 100644 index 0000000000..48cbeb1d7b --- /dev/null +++ b/public/extensions/core/interfaces/toggle/Interface.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=79)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,a,i,s){var u=typeof(e=e||{}).default;"object"!==u&&"function"!==u||(e=e.default);var l,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),a&&(c._scopeId=a),i?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(i)},c._ssrRegister=l):o&&(l=s?function(){o.call(this,this.$root.$options.shadowRoot)}:o),l)if(c.functional){c._injectStyles=l;var d=c.render;c.render=function(e,t){return l.call(t),d(e,t)}}else{var f=c.beforeCreate;c.beforeCreate=f?[].concat(f,l):[l]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},104:function(e,t,n){(e.exports=n(3)(!1)).push([e.i,'span[data-v-d7e41b6a]{position:relative}input[data-v-d7e41b6a]{position:absolute;width:100%;height:100%;left:0;top:0;opacity:0;z-index:2}.toggle[data-v-d7e41b6a]{position:relative;cursor:pointer}.toggle label[data-v-d7e41b6a]{padding:1rem 0 1rem 2.75rem;position:relative}.toggle label[data-v-d7e41b6a]:after,.toggle label[data-v-d7e41b6a]:before{content:"";position:absolute;margin:0;outline:0;top:50%;transform:translateY(-50%);transition:all .3s var(--transition)}.toggle label[data-v-d7e41b6a]:before{left:.0625rem;width:2.125rem;height:.875rem;border-radius:.5rem;background-color:var(--gray)}.toggle label[data-v-d7e41b6a]:after{left:0;width:1.25rem;height:1.25rem;background-color:var(--lightest-gray);border-radius:50%;box-shadow:0 3px 1px -2px rgba(0,0,0,.14),0 2px 2px 0 rgba(0,0,0,.098),0 1px 5px 0 rgba(0,0,0,.084)}.toggle input:checked+label[data-v-d7e41b6a]:before{background-color:var(--accent);opacity:.4}.toggle input:checked+label[data-v-d7e41b6a]:after{background-color:var(--accent);transform:translate(80%,-50%)}',""])},105:function(e,t,n){var r=n(104);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals),(0,n(4).default)("be7f7d66",r,!0,{})},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o0?r-4:r,f=0;f>16&255,a[s++]=e>>8&255,a[s++]=255&e;return 2===l&&(e=o[t.charCodeAt(f)]<<2|o[t.charCodeAt(f+1)]>>4,a[s++]=255&e),1===l&&(e=o[t.charCodeAt(f)]<<10|o[t.charCodeAt(f+1)]<<4|o[t.charCodeAt(f+2)]>>2,a[s++]=e>>8&255,a[s++]=255&e),a},e.fromByteArray=function(t){for(var e,n=t.length,o=n%3,i=[],l=0,a=n-o;la?a:l+16383));return 1===o?(e=t[n-1],i.push(r[e>>2]+r[e<<4&63]+"==")):2===o&&(e=(t[n-2]<<8)+t[n-1],i.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"=")),i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,s=l.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var n=t.indexOf("=");return-1===n&&(n=e),[n,n===e?0:4-n%4]}function c(t,e,n){for(var o,i,l=[],a=e;a>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return l.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},101:function(t,e,n){"use strict";(function(t){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +var r=n(100),o=n(99),i=n(98);function l(){return s.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function a(t,e){if(l()=l())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+l().toString(16)+" bytes");return 0|t}function p(t,e){if(s.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var n=t.length;if(0===n)return 0;for(var r=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return D(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(t).length;default:if(r)return D(t).length;e=(""+e).toLowerCase(),r=!0}}function y(t,e,n){var r=t[e];t[e]=t[n],t[n]=r}function v(t,e,n,r,o){if(0===t.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(o)return-1;n=t.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof e&&(e=s.from(e,r)),s.isBuffer(e))return 0===e.length?-1:b(t,e,n,r,o);if("number"==typeof e)return e&=255,s.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):b(t,[e],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(t,e,n,r,o){var i,l=1,a=t.length,s=e.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(t.length<2||e.length<2)return-1;l=2,a/=2,s/=2,n/=2}function u(t,e){return 1===l?t[e]:t.readUInt16BE(e*l)}if(o){var c=-1;for(i=n;ia&&(n=a-s),i=n;i>=0;i--){for(var f=!0,d=0;do&&(r=o):r=o;var i=e.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var l=0;l>8,o=n%256,i.push(o),i.push(r);return i}(e,t.length-n),t,n,r)}function O(t,e,n){return 0===e&&n===t.length?r.fromByteArray(t):r.fromByteArray(t.slice(e,n))}function x(t,e,n){n=Math.min(t.length,n);for(var r=[],o=e;o239?4:u>223?3:u>191?2:1;if(o+f<=n)switch(f){case 1:u<128&&(c=u);break;case 2:128==(192&(i=t[o+1]))&&(s=(31&u)<<6|63&i)>127&&(c=s);break;case 3:i=t[o+1],l=t[o+2],128==(192&i)&&128==(192&l)&&(s=(15&u)<<12|(63&i)<<6|63&l)>2047&&(s<55296||s>57343)&&(c=s);break;case 4:i=t[o+1],l=t[o+2],a=t[o+3],128==(192&i)&&128==(192&l)&&128==(192&a)&&(s=(15&u)<<18|(63&i)<<12|(63&l)<<6|63&a)>65535&&s<1114112&&(c=s)}null===c?(c=65533,f=1):c>65535&&(c-=65536,r.push(c>>>10&1023|55296),c=56320|1023&c),r.push(c),o+=f}return function(t){var e=t.length;if(e<=E)return String.fromCharCode.apply(String,t);for(var n="",r=0;rthis.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return j(this,e,n);case"utf8":case"utf-8":return x(this,e,n);case"ascii":return A(this,e,n);case"latin1":case"binary":return N(this,e,n);case"base64":return O(this,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,e,n);default:if(r)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),r=!0}}.apply(this,arguments)},s.prototype.equals=function(t){if(!s.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===s.compare(this,t)},s.prototype.inspect=function(){var t="",n=e.INSPECT_MAX_BYTES;return this.length>0&&(t=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(t+=" ... ")),""},s.prototype.compare=function(t,e,n,r,o){if(!s.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),e<0||n>t.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&e>=n)return 0;if(r>=o)return-1;if(e>=n)return 1;if(e>>>=0,n>>>=0,r>>>=0,o>>>=0,this===t)return 0;for(var i=o-r,l=n-e,a=Math.min(i,l),u=this.slice(r,o),c=t.slice(e,n),f=0;fo)&&(n=o),t.length>0&&(n<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return g(this,t,e,n);case"utf8":case"utf-8":return m(this,t,e,n);case"ascii":return q(this,t,e,n);case"latin1":case"binary":return w(this,t,e,n);case"base64":return _(this,t,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return k(this,t,e,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},s.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var E=4096;function A(t,e,n){var r="";n=Math.min(t.length,n);for(var o=e;or)&&(n=r);for(var o="",i=e;in)throw new RangeError("Trying to access beyond buffer length")}function P(t,e,n,r,o,i){if(!s.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>o||et.length)throw new RangeError("Index out of range")}function C(t,e,n,r){e<0&&(e=65535+e+1);for(var o=0,i=Math.min(t.length-n,2);o>>8*(r?o:1-o)}function L(t,e,n,r){e<0&&(e=4294967295+e+1);for(var o=0,i=Math.min(t.length-n,4);o>>8*(r?o:3-o)&255}function R(t,e,n,r,o,i){if(n+r>t.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function M(t,e,n,r,i){return i||R(t,0,n,4),o.write(t,e,n,r,23,4),n+4}function I(t,e,n,r,i){return i||R(t,0,n,8),o.write(t,e,n,r,52,8),n+8}s.prototype.slice=function(t,e){var n,r=this.length;if(t=~~t,e=void 0===e?r:~~e,t<0?(t+=r)<0&&(t=0):t>r&&(t=r),e<0?(e+=r)<0&&(e=0):e>r&&(e=r),e0&&(o*=256);)r+=this[t+--e]*o;return r},s.prototype.readUInt8=function(t,e){return e||S(t,1,this.length),this[t]},s.prototype.readUInt16LE=function(t,e){return e||S(t,2,this.length),this[t]|this[t+1]<<8},s.prototype.readUInt16BE=function(t,e){return e||S(t,2,this.length),this[t]<<8|this[t+1]},s.prototype.readUInt32LE=function(t,e){return e||S(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},s.prototype.readUInt32BE=function(t,e){return e||S(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},s.prototype.readIntLE=function(t,e,n){t|=0,e|=0,n||S(t,e,this.length);for(var r=this[t],o=1,i=0;++i=(o*=128)&&(r-=Math.pow(2,8*e)),r},s.prototype.readIntBE=function(t,e,n){t|=0,e|=0,n||S(t,e,this.length);for(var r=e,o=1,i=this[t+--r];r>0&&(o*=256);)i+=this[t+--r]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*e)),i},s.prototype.readInt8=function(t,e){return e||S(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},s.prototype.readInt16LE=function(t,e){e||S(t,2,this.length);var n=this[t]|this[t+1]<<8;return 32768&n?4294901760|n:n},s.prototype.readInt16BE=function(t,e){e||S(t,2,this.length);var n=this[t+1]|this[t]<<8;return 32768&n?4294901760|n:n},s.prototype.readInt32LE=function(t,e){return e||S(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},s.prototype.readInt32BE=function(t,e){return e||S(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},s.prototype.readFloatLE=function(t,e){return e||S(t,4,this.length),o.read(this,t,!0,23,4)},s.prototype.readFloatBE=function(t,e){return e||S(t,4,this.length),o.read(this,t,!1,23,4)},s.prototype.readDoubleLE=function(t,e){return e||S(t,8,this.length),o.read(this,t,!0,52,8)},s.prototype.readDoubleBE=function(t,e){return e||S(t,8,this.length),o.read(this,t,!1,52,8)},s.prototype.writeUIntLE=function(t,e,n,r){t=+t,e|=0,n|=0,r||P(this,t,e,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[e]=255&t;++i=0&&(i*=256);)this[e+o]=t/i&255;return e+n},s.prototype.writeUInt8=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,1,255,0),s.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},s.prototype.writeUInt16LE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,2,65535,0),s.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):C(this,t,e,!0),e+2},s.prototype.writeUInt16BE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,2,65535,0),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):C(this,t,e,!1),e+2},s.prototype.writeUInt32LE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,4,4294967295,0),s.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):L(this,t,e,!0),e+4},s.prototype.writeUInt32BE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,4,4294967295,0),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):L(this,t,e,!1),e+4},s.prototype.writeIntLE=function(t,e,n,r){if(t=+t,e|=0,!r){var o=Math.pow(2,8*n-1);P(this,t,e,n,o-1,-o)}var i=0,l=1,a=0;for(this[e]=255&t;++i>0)-a&255;return e+n},s.prototype.writeIntBE=function(t,e,n,r){if(t=+t,e|=0,!r){var o=Math.pow(2,8*n-1);P(this,t,e,n,o-1,-o)}var i=n-1,l=1,a=0;for(this[e+i]=255&t;--i>=0&&(l*=256);)t<0&&0===a&&0!==this[e+i+1]&&(a=1),this[e+i]=(t/l>>0)-a&255;return e+n},s.prototype.writeInt8=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,1,127,-128),s.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},s.prototype.writeInt16LE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,2,32767,-32768),s.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):C(this,t,e,!0),e+2},s.prototype.writeInt16BE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,2,32767,-32768),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):C(this,t,e,!1),e+2},s.prototype.writeInt32LE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,4,2147483647,-2147483648),s.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):L(this,t,e,!0),e+4},s.prototype.writeInt32BE=function(t,e,n){return t=+t,e|=0,n||P(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),s.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):L(this,t,e,!1),e+4},s.prototype.writeFloatLE=function(t,e,n){return M(this,t,e,!0,n)},s.prototype.writeFloatBE=function(t,e,n){return M(this,t,e,!1,n)},s.prototype.writeDoubleLE=function(t,e,n){return I(this,t,e,!0,n)},s.prototype.writeDoubleBE=function(t,e,n){return I(this,t,e,!1,n)},s.prototype.copy=function(t,e,n,r){if(n||(n=0),r||0===r||(r=this.length),e>=t.length&&(e=t.length),e||(e=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),t.length-e=0;--o)t[o+e]=this[o+n];else if(i<1e3||!s.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(i=e;i55295&&n<57344){if(!o){if(n>56319){(e-=3)>-1&&i.push(239,191,189);continue}if(l+1===r){(e-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(e-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(e-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((e-=1)<0)break;i.push(n)}else if(n<2048){if((e-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((e-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function z(t){return r.toByteArray(function(t){if((t=function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}(t).replace(B,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function F(t,e,n,r){for(var o=0;o=e.length||o>=t.length);++o)e[o+n]=t[o];return o}}).call(this,n(14))},11:function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},14:function(t,e){var n,r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(t){"object"===("undefined"==typeof window?"undefined":r(window))&&(n=window)}t.exports=n},23:function(t,e,n){(function(t,n){var r,o,i,l,a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};"undefined"!=typeof self&&self, +/*! + * Quill Editor v1.3.6 + * https://quilljs.com/ + * Copyright (c) 2014, Jason Chen + * Copyright (c) 2013, salesforce.com + */ +l=function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=109)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(17),o=n(18),i=n(19),l=n(45),a=n(46),s=n(47),u=n(48),c=n(49),f=n(12),d=n(32),h=n(33),p=n(31),y=n(1),v={Scope:y.Scope,create:y.create,find:y.find,query:y.query,register:y.register,Container:r.default,Format:o.default,Leaf:i.default,Embed:u.default,Scroll:l.default,Block:s.default,Inline:a.default,Text:c.default,Attributor:{Attribute:f.default,Class:d.default,Style:h.default,Store:p.default}};e.default=v},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0});var i=function(t){function e(e){var n=this;return e="[Parchment] "+e,(n=t.call(this,e)||this).message=e,n.name=n.constructor.name,n}return o(e,t),e}(Error);e.ParchmentError=i;var l,a={},s={},u={},c={};function f(t,e){var n;if(void 0===e&&(e=l.ANY),"string"==typeof t)n=c[t]||a[t];else if(t instanceof Text||t.nodeType===Node.TEXT_NODE)n=c.text;else if("number"==typeof t)t&l.LEVEL&l.BLOCK?n=c.block:t&l.LEVEL&l.INLINE&&(n=c.inline);else if(t instanceof HTMLElement){var r=(t.getAttribute("class")||"").split(/\s+/);for(var o in r)if(n=s[r[o]])break;n=n||u[t.tagName]}return null==n?null:e&l.LEVEL&n.scope&&e&l.TYPE&n.scope?n:null}e.DATA_KEY="__blot",function(t){t[t.TYPE=3]="TYPE",t[t.LEVEL=12]="LEVEL",t[t.ATTRIBUTE=13]="ATTRIBUTE",t[t.BLOT=14]="BLOT",t[t.INLINE=7]="INLINE",t[t.BLOCK=11]="BLOCK",t[t.BLOCK_BLOT=10]="BLOCK_BLOT",t[t.INLINE_BLOT=6]="INLINE_BLOT",t[t.BLOCK_ATTRIBUTE=9]="BLOCK_ATTRIBUTE",t[t.INLINE_ATTRIBUTE=5]="INLINE_ATTRIBUTE",t[t.ANY=15]="ANY"}(l=e.Scope||(e.Scope={})),e.create=function(t,e){var n=f(t);if(null==n)throw new i("Unable to create "+t+" blot");var r=n;return new r(t instanceof Node||t.nodeType===Node.TEXT_NODE?t:r.create(e),e)},e.find=function t(n,r){return void 0===r&&(r=!1),null==n?null:null!=n[e.DATA_KEY]?n[e.DATA_KEY].blot:r?t(n.parentNode,r):null},e.query=f,e.register=function t(){for(var e=[],n=0;n1)return e.map(function(e){return t(e)});var r=e[0];if("string"!=typeof r.blotName&&"string"!=typeof r.attrName)throw new i("Invalid definition");if("abstract"===r.blotName)throw new i("Cannot register abstract class");return c[r.blotName||r.attrName]=r,"string"==typeof r.keyName?a[r.keyName]=r:(null!=r.className&&(s[r.className]=r),null!=r.tagName&&(Array.isArray(r.tagName)?r.tagName=r.tagName.map(function(t){return t.toUpperCase()}):r.tagName=r.tagName.toUpperCase(),(Array.isArray(r.tagName)?r.tagName:[r.tagName]).forEach(function(t){null!=u[t]&&null!=r.className||(u[t]=r)}))),r}},function(t,e,n){var r=n(51),o=n(11),i=n(3),l=n(20),s=String.fromCharCode(0),u=function(t){Array.isArray(t)?this.ops=t:null!=t&&Array.isArray(t.ops)?this.ops=t.ops:this.ops=[]};u.prototype.insert=function(t,e){var n={};return 0===t.length?this:(n.insert=t,null!=e&&"object"===(void 0===e?"undefined":a(e))&&Object.keys(e).length>0&&(n.attributes=e),this.push(n))},u.prototype.delete=function(t){return t<=0?this:this.push({delete:t})},u.prototype.retain=function(t,e){if(t<=0)return this;var n={retain:t};return null!=e&&"object"===(void 0===e?"undefined":a(e))&&Object.keys(e).length>0&&(n.attributes=e),this.push(n)},u.prototype.push=function(t){var e=this.ops.length,n=this.ops[e-1];if(t=i(!0,{},t),"object"===(void 0===n?"undefined":a(n))){if("number"==typeof t.delete&&"number"==typeof n.delete)return this.ops[e-1]={delete:n.delete+t.delete},this;if("number"==typeof n.delete&&null!=t.insert&&(e-=1,"object"!==(void 0===(n=this.ops[e-1])?"undefined":a(n))))return this.ops.unshift(t),this;if(o(t.attributes,n.attributes)){if("string"==typeof t.insert&&"string"==typeof n.insert)return this.ops[e-1]={insert:n.insert+t.insert},"object"===a(t.attributes)&&(this.ops[e-1].attributes=t.attributes),this;if("number"==typeof t.retain&&"number"==typeof n.retain)return this.ops[e-1]={retain:n.retain+t.retain},"object"===a(t.attributes)&&(this.ops[e-1].attributes=t.attributes),this}}return e===this.ops.length?this.ops.push(t):this.ops.splice(e,0,t),this},u.prototype.chop=function(){var t=this.ops[this.ops.length-1];return t&&t.retain&&!t.attributes&&this.ops.pop(),this},u.prototype.filter=function(t){return this.ops.filter(t)},u.prototype.forEach=function(t){this.ops.forEach(t)},u.prototype.map=function(t){return this.ops.map(t)},u.prototype.partition=function(t){var e=[],n=[];return this.forEach(function(r){(t(r)?e:n).push(r)}),[e,n]},u.prototype.reduce=function(t,e){return this.ops.reduce(t,e)},u.prototype.changeLength=function(){return this.reduce(function(t,e){return e.insert?t+l.length(e):e.delete?t-e.delete:t},0)},u.prototype.length=function(){return this.reduce(function(t,e){return t+l.length(e)},0)},u.prototype.slice=function(t,e){t=t||0,"number"!=typeof e&&(e=1/0);for(var n=[],r=l.iterator(this.ops),o=0;o0&&(e.push(t.ops[0]),e.ops=e.ops.concat(t.ops.slice(1))),e},u.prototype.diff=function(t,e){if(this.ops===t.ops)return new u;var n=[this,t].map(function(e){return e.map(function(n){if(null!=n.insert)return"string"==typeof n.insert?n.insert:s;throw new Error("diff() called "+(e===t?"on":"with")+" non-document")}).join("")}),i=new u,a=r(n[0],n[1],e),c=l.iterator(this.ops),f=l.iterator(t.ops);return a.forEach(function(t){for(var e=t[1].length;e>0;){var n=0;switch(t[0]){case r.INSERT:n=Math.min(f.peekLength(),e),i.push(f.next(n));break;case r.DELETE:n=Math.min(e,c.peekLength()),c.next(n),i.delete(n);break;case r.EQUAL:n=Math.min(c.peekLength(),f.peekLength(),e);var a=c.next(n),s=f.next(n);o(a.insert,s.insert)?i.retain(n,l.attributes.diff(a.attributes,s.attributes)):i.push(s).delete(n)}e-=n}}),i.chop()},u.prototype.eachLine=function(t,e){e=e||"\n";for(var n=l.iterator(this.ops),r=new u,o=0;n.hasNext();){if("insert"!==n.peekType())return;var i=n.peek(),a=l.length(i)-n.peekLength(),s="string"==typeof i.insert?i.insert.indexOf(e,a)-a:-1;if(s<0)r.push(n.next());else if(s>0)r.push(n.next(s));else{if(!1===t(r,n.next(1).attributes||{},o))return;o+=1,r=new u}}r.length()>0&&t(r,{},o)},u.prototype.transform=function(t,e){if(e=!!e,"number"==typeof t)return this.transformPosition(t,e);for(var n=l.iterator(this.ops),r=l.iterator(t.ops),o=new u;n.hasNext()||r.hasNext();)if("insert"!==n.peekType()||!e&&"insert"===r.peekType())if("insert"===r.peekType())o.push(r.next());else{var i=Math.min(n.peekLength(),r.peekLength()),a=n.next(i),s=r.next(i);if(a.delete)continue;s.delete?o.push(s):o.retain(i,l.attributes.transform(a.attributes,s.attributes,e))}else o.retain(l.length(n.next()));return o.chop()},u.prototype.transformPosition=function(t,e){e=!!e;for(var n=l.iterator(this.ops),r=0;n.hasNext()&&r<=t;){var o=n.peekLength(),i=n.peekType();n.next(),"delete"!==i?("insert"===i&&(r0&&(t1&&void 0!==arguments[1]&&arguments[1];if(n&&(0===t||t>=this.length()-1)){var r=this.clone();return 0===t?(this.parent.insertBefore(r,this),this):(this.parent.insertBefore(r,this.next),r)}var i=o(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"split",this).call(this,t,n);return this.cache={},i}}]),e}();function g(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return null==t?e:("function"==typeof t.formats&&(e=(0,i.default)(e,t.formats())),null==t.parent||"scroll"==t.parent.blotName||t.parent.statics.scope!==t.statics.scope?e:g(t.parent,e))}b.blotName="block",b.tagName="P",b.defaultChild="break",b.allowedChildren=[c.default,s.default.Embed,f.default],e.bubbleFormats=g,e.BlockEmbed=v,e.default=b},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.overload=e.expandConfig=void 0;var r="function"==typeof Symbol&&"symbol"===a(Symbol.iterator)?function(t){return void 0===t?"undefined":a(t)}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":void 0===t?"undefined":a(t)},o=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},i=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};if(function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.options=w(e,r),this.container=this.options.container,null==this.container)return m.error("Invalid Quill container",e);this.options.debug&&t.debug(this.options.debug);var o=this.container.innerHTML.trim();this.container.classList.add("ql-container"),this.container.innerHTML="",this.container.__quill=this,this.root=this.addContainer("ql-editor"),this.root.classList.add("ql-blank"),this.root.setAttribute("data-gramm",!1),this.scrollingContainer=this.options.scrollingContainer||this.root,this.emitter=new u.default,this.scroll=f.default.create(this.root,{emitter:this.emitter,whitelist:this.options.formats}),this.editor=new s.default(this.scroll),this.selection=new h.default(this.scroll,this.emitter),this.theme=new this.options.theme(this,this.options),this.keyboard=this.theme.addModule("keyboard"),this.clipboard=this.theme.addModule("clipboard"),this.history=this.theme.addModule("history"),this.theme.init(),this.emitter.on(u.default.events.EDITOR_CHANGE,function(t){t===u.default.events.TEXT_CHANGE&&n.root.classList.toggle("ql-blank",n.editor.isBlank())}),this.emitter.on(u.default.events.SCROLL_UPDATE,function(t,e){var r=n.selection.lastRange,o=r&&0===r.length?r.index:void 0;_.call(n,function(){return n.editor.update(null,e,o)},t)});var i=this.clipboard.convert("
    "+o+"


    ");this.setContents(i),this.history.clear(),this.options.placeholder&&this.root.setAttribute("data-placeholder",this.options.placeholder),this.options.readOnly&&this.disable()}return i(t,null,[{key:"debug",value:function(t){!0===t&&(t="log"),y.default.level(t)}},{key:"find",value:function(t){return t.__quill||f.default.find(t)}},{key:"import",value:function(t){return null==this.imports[t]&&m.error("Cannot import "+t+". Are you sure it was registered?"),this.imports[t]}},{key:"register",value:function(t,e){var n=this,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if("string"!=typeof t){var o=t.attrName||t.blotName;"string"==typeof o?this.register("formats/"+o,t,e):Object.keys(t).forEach(function(r){n.register(r,t[r],e)})}else null==this.imports[t]||r||m.warn("Overwriting "+t+" with",e),this.imports[t]=e,(t.startsWith("blots/")||t.startsWith("formats/"))&&"abstract"!==e.blotName?f.default.register(e):t.startsWith("modules")&&"function"==typeof e.register&&e.register()}}]),i(t,[{key:"addContainer",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if("string"==typeof t){var n=t;(t=document.createElement("div")).classList.add(n)}return this.container.insertBefore(t,e),t}},{key:"blur",value:function(){this.selection.setRange(null)}},{key:"deleteText",value:function(t,e,n){var r=this,i=k(t,e,n),l=o(i,4);return t=l[0],e=l[1],n=l[3],_.call(this,function(){return r.editor.deleteText(t,e)},n,t,-1*e)}},{key:"disable",value:function(){this.enable(!1)}},{key:"enable",value:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.scroll.enable(t),this.container.classList.toggle("ql-disabled",!t)}},{key:"focus",value:function(){var t=this.scrollingContainer.scrollTop;this.selection.focus(),this.scrollingContainer.scrollTop=t,this.scrollIntoView()}},{key:"format",value:function(t,e){var n=this,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:u.default.sources.API;return _.call(this,function(){var r=n.getSelection(!0),o=new l.default;if(null==r)return o;if(f.default.query(t,f.default.Scope.BLOCK))o=n.editor.formatLine(r.index,r.length,g({},t,e));else{if(0===r.length)return n.selection.format(t,e),o;o=n.editor.formatText(r.index,r.length,g({},t,e))}return n.setSelection(r,u.default.sources.SILENT),o},r)}},{key:"formatLine",value:function(t,e,n,r,i){var l,a=this,s=k(t,e,n,r,i),u=o(s,4);return t=u[0],e=u[1],l=u[2],i=u[3],_.call(this,function(){return a.editor.formatLine(t,e,l)},i,t,0)}},{key:"formatText",value:function(t,e,n,r,i){var l,a=this,s=k(t,e,n,r,i),u=o(s,4);return t=u[0],e=u[1],l=u[2],i=u[3],_.call(this,function(){return a.editor.formatText(t,e,l)},i,t,0)}},{key:"getBounds",value:function(t){var e,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;e="number"==typeof t?this.selection.getBounds(t,n):this.selection.getBounds(t.index,t.length);var r=this.container.getBoundingClientRect();return{bottom:e.bottom-r.top,height:e.height,left:e.left-r.left,right:e.right-r.left,top:e.top-r.top,width:e.width}}},{key:"getContents",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.getLength()-t,n=k(t,e),r=o(n,2);return t=r[0],e=r[1],this.editor.getContents(t,e)}},{key:"getFormat",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.getSelection(!0),e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return"number"==typeof t?this.editor.getFormat(t,e):this.editor.getFormat(t.index,t.length)}},{key:"getIndex",value:function(t){return t.offset(this.scroll)}},{key:"getLength",value:function(){return this.scroll.length()}},{key:"getLeaf",value:function(t){return this.scroll.leaf(t)}},{key:"getLine",value:function(t){return this.scroll.line(t)}},{key:"getLines",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Number.MAX_VALUE;return"number"!=typeof t?this.scroll.lines(t.index,t.length):this.scroll.lines(t,e)}},{key:"getModule",value:function(t){return this.theme.modules[t]}},{key:"getSelection",value:function(){return arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&this.focus(),this.update(),this.selection.getRange()[0]}},{key:"getText",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.getLength()-t,n=k(t,e),r=o(n,2);return t=r[0],e=r[1],this.editor.getText(t,e)}},{key:"hasFocus",value:function(){return this.selection.hasFocus()}},{key:"insertEmbed",value:function(e,n,r){var o=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:t.sources.API;return _.call(this,function(){return o.editor.insertEmbed(e,n,r)},i,e)}},{key:"insertText",value:function(t,e,n,r,i){var l,a=this,s=k(t,0,n,r,i),u=o(s,4);return t=u[0],l=u[2],i=u[3],_.call(this,function(){return a.editor.insertText(t,e,l)},i,t,e.length)}},{key:"isEnabled",value:function(){return!this.container.classList.contains("ql-disabled")}},{key:"off",value:function(){return this.emitter.off.apply(this.emitter,arguments)}},{key:"on",value:function(){return this.emitter.on.apply(this.emitter,arguments)}},{key:"once",value:function(){return this.emitter.once.apply(this.emitter,arguments)}},{key:"pasteHTML",value:function(t,e,n){this.clipboard.dangerouslyPasteHTML(t,e,n)}},{key:"removeFormat",value:function(t,e,n){var r=this,i=k(t,e,n),l=o(i,4);return t=l[0],e=l[1],n=l[3],_.call(this,function(){return r.editor.removeFormat(t,e)},n,t)}},{key:"scrollIntoView",value:function(){this.selection.scrollIntoView(this.scrollingContainer)}},{key:"setContents",value:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:u.default.sources.API;return _.call(this,function(){t=new l.default(t);var n=e.getLength(),r=e.editor.deleteText(0,n),o=e.editor.applyDelta(t),i=o.ops[o.ops.length-1];return null!=i&&"string"==typeof i.insert&&"\n"===i.insert[i.insert.length-1]&&(e.editor.deleteText(e.getLength()-1,1),o.delete(1)),r.compose(o)},n)}},{key:"setSelection",value:function(e,n,r){if(null==e)this.selection.setRange(null,n||t.sources.API);else{var i=k(e,n,r),l=o(i,4);e=l[0],n=l[1],r=l[3],this.selection.setRange(new d.Range(e,n),r),r!==u.default.sources.SILENT&&this.selection.scrollIntoView(this.scrollingContainer)}}},{key:"setText",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:u.default.sources.API,n=(new l.default).insert(t);return this.setContents(n,e)}},{key:"update",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:u.default.sources.USER,e=this.scroll.update(t);return this.selection.update(t),e}},{key:"updateContents",value:function(t){var e=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:u.default.sources.API;return _.call(this,function(){return t=new l.default(t),e.editor.applyDelta(t,n)},n,!0)}}]),t}();function w(t,e){if((e=(0,p.default)(!0,{container:t,modules:{clipboard:!0,keyboard:!0,history:!0}},e)).theme&&e.theme!==q.DEFAULTS.theme){if(e.theme=q.import("themes/"+e.theme),null==e.theme)throw new Error("Invalid theme "+e.theme+". Did you register it?")}else e.theme=v.default;var n=(0,p.default)(!0,{},e.theme.DEFAULTS);[n,e].forEach(function(t){t.modules=t.modules||{},Object.keys(t.modules).forEach(function(e){!0===t.modules[e]&&(t.modules[e]={})})});var r=Object.keys(n.modules).concat(Object.keys(e.modules)).reduce(function(t,e){var n=q.import("modules/"+e);return null==n?m.error("Cannot load "+e+" module. Are you sure you registered it?"):t[e]=n.DEFAULTS||{},t},{});return null!=e.modules&&e.modules.toolbar&&e.modules.toolbar.constructor!==Object&&(e.modules.toolbar={container:e.modules.toolbar}),e=(0,p.default)(!0,{},q.DEFAULTS,{modules:r},n,e),["bounds","container","scrollingContainer"].forEach(function(t){"string"==typeof e[t]&&(e[t]=document.querySelector(e[t]))}),e.modules=Object.keys(e.modules).reduce(function(t,n){return e.modules[n]&&(t[n]=e.modules[n]),t},{}),e}function _(t,e,n,r){if(this.options.strict&&!this.isEnabled()&&e===u.default.sources.USER)return new l.default;var o=null==n?null:this.getSelection(),i=this.editor.delta,a=t();if(null!=o&&(!0===n&&(n=o.index),null==r?o=O(o,a,e):0!==r&&(o=O(o,n,r,e)),this.setSelection(o,u.default.sources.SILENT)),a.length()>0){var s,c,f=[u.default.events.TEXT_CHANGE,a,i,e];(s=this.emitter).emit.apply(s,[u.default.events.EDITOR_CHANGE].concat(f)),e!==u.default.sources.SILENT&&(c=this.emitter).emit.apply(c,f)}return a}function k(t,e,n,o,i){var l={};return"number"==typeof t.index&&"number"==typeof t.length?"number"!=typeof e?(i=o,o=n,n=e,e=t.length,t=t.index):(e=t.length,t=t.index):"number"!=typeof e&&(i=o,o=n,n=e,e=0),"object"===(void 0===n?"undefined":r(n))?(l=n,i=o):"string"==typeof n&&(null!=o?l[n]=o:i=n),[t,e,l,i=i||u.default.sources.API]}function O(t,e,n,r){if(null==t)return null;var i=void 0,a=void 0;if(e instanceof l.default){var s=[t.index,t.index+t.length].map(function(t){return e.transformPosition(t,r!==u.default.sources.USER)}),c=o(s,2);i=c[0],a=c[1]}else{var f=[t.index,t.index+t.length].map(function(t){return t=0?t+n:Math.max(e,t+n)}),h=o(f,2);i=h[0],a=h[1]}return new d.Range(i,a-i)}q.DEFAULTS={bounds:null,formats:null,modules:{},placeholder:"",readOnly:!1,scrollingContainer:null,strict:!0,theme:"default"},q.events=u.default.events,q.sources=u.default.sources,q.version="1.3.6",q.imports={delta:l.default,parchment:f.default,"core/module":c.default,"core/theme":v.default},e.expandConfig=w,e.overload=k,e.default=q},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;n0){var n=this.parent.isolate(this.offset(),this.length());this.moveChildren(n),n.wrap(this)}}}],[{key:"compare",value:function(t,n){var r=e.order.indexOf(t),o=e.order.indexOf(n);return r>=0||o>=0?r-o:t===n?0:t1?e-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:{};!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.quill=e,this.options=n};r.DEFAULTS={},e.default=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=["error","warn","log","info"],o="warn";function i(t){if(r.indexOf(t)<=r.indexOf(o)){for(var e,n=arguments.length,i=Array(n>1?n-1:0),l=1;l=0;c--)if(d[c]!=h[c])return!1;for(c=d.length-1;c>=0;c--)if(f=d[c],!l(t[f],e[f],n))return!1;return(void 0===t?"undefined":a(t))===(void 0===e?"undefined":a(e))}(t,e,n))};function s(t){return null===t||void 0===t}function u(t){return!(!t||"object"!==(void 0===t?"undefined":a(t))||"number"!=typeof t.length||"function"!=typeof t.copy||"function"!=typeof t.slice||t.length>0&&"number"!=typeof t[0])}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(1),o=function(){function t(t,e,n){void 0===n&&(n={}),this.attrName=t,this.keyName=e;var o=r.Scope.TYPE&r.Scope.ATTRIBUTE;null!=n.scope?this.scope=n.scope&r.Scope.LEVEL|o:this.scope=r.Scope.ATTRIBUTE,null!=n.whitelist&&(this.whitelist=n.whitelist)}return t.keys=function(t){return[].map.call(t.attributes,function(t){return t.name})},t.prototype.add=function(t,e){return!!this.canAdd(t,e)&&(t.setAttribute(this.keyName,e),!0)},t.prototype.canAdd=function(t,e){return null!=r.query(t,r.Scope.BLOT&(this.scope|r.Scope.TYPE))&&(null==this.whitelist||("string"==typeof e?this.whitelist.indexOf(e.replace(/["']/g,""))>-1:this.whitelist.indexOf(e)>-1))},t.prototype.remove=function(t){t.removeAttribute(this.keyName)},t.prototype.value=function(t){var e=t.getAttribute(this.keyName);return this.canAdd(t,e)&&e?e:""},t}();e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.Code=void 0;var r=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},o=function(){function t(t,e){for(var n=0;n=t+n)){var l=this.newlineIndex(t,!0)+1,a=i-l+1,u=this.isolate(l,a),c=u.next;u.format(r,o),c instanceof e&&c.formatAt(0,t-l+n-a,r,o)}}}},{key:"insertAt",value:function(t,e,n){if(null==n){var o=this.descendant(f.default,t),i=r(o,2),l=i[0],a=i[1];l.insertAt(a,e)}}},{key:"length",value:function(){var t=this.domNode.textContent.length;return this.domNode.textContent.endsWith("\n")?t:t+1}},{key:"newlineIndex",value:function(t){if(arguments.length>1&&void 0!==arguments[1]&&arguments[1])return this.domNode.textContent.slice(0,t).lastIndexOf("\n");var e=this.domNode.textContent.slice(t).indexOf("\n");return e>-1?t+e:-1}},{key:"optimize",value:function(t){this.domNode.textContent.endsWith("\n")||this.appendChild(s.default.create("text","\n")),i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"optimize",this).call(this,t);var n=this.next;null!=n&&n.prev===this&&n.statics.blotName===this.statics.blotName&&this.statics.formats(this.domNode)===n.statics.formats(n.domNode)&&(n.optimize(t),n.moveChildren(this),n.remove())}},{key:"replace",value:function(t){i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"replace",this).call(this,t),[].slice.call(this.domNode.querySelectorAll("*")).forEach(function(t){var e=s.default.find(t);null==e?t.parentNode.removeChild(t):e instanceof s.default.Embed?e.remove():e.unwrap()})}}],[{key:"create",value:function(t){var n=i(e.__proto__||Object.getPrototypeOf(e),"create",this).call(this,t);return n.setAttribute("spellcheck",!1),n}},{key:"formats",value:function(){return!0}}]),e}();b.blotName="code-block",b.tagName="PRE",b.TAB=" ",e.Code=v,e.default=b},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r="function"==typeof Symbol&&"symbol"===a(Symbol.iterator)?function(t){return void 0===t?"undefined":a(t)}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":void 0===t?"undefined":a(t)},o=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},i=function(){function t(t,e){for(var n=0;n=i&&!f.endsWith("\n")&&(n=!0),e.scroll.insertAt(t,f);var p=e.scroll.line(t),y=o(p,2),v=y[0],g=y[1],m=(0,b.default)({},(0,d.bubbleFormats)(v));if(v instanceof h.default){var q=v.descendant(u.default.Leaf,g),w=o(q,1)[0];m=(0,b.default)(m,(0,d.bubbleFormats)(w))}c=s.default.attributes.diff(m,c)||{}}else if("object"===r(l.insert)){var _=Object.keys(l.insert)[0];if(null==_)return t;e.scroll.insertAt(t,_,l.insert[_])}i+=a}return Object.keys(c).forEach(function(n){e.scroll.formatAt(t,a,n,c[n])}),t+a},0),t.reduce(function(t,n){return"number"==typeof n.delete?(e.scroll.deleteAt(t,n.delete),t):t+(n.retain||n.insert.length||1)},0),this.scroll.batchEnd(),this.update(t)}},{key:"deleteText",value:function(t,e){return this.scroll.deleteAt(t,e),this.update((new l.default).retain(t).delete(e))}},{key:"formatLine",value:function(t,e){var n=this,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.scroll.update(),Object.keys(r).forEach(function(o){if(null==n.scroll.whitelist||n.scroll.whitelist[o]){var i=n.scroll.lines(t,Math.max(e,1)),l=e;i.forEach(function(e){var i=e.length();if(e instanceof c.default){var a=t-e.offset(n.scroll),s=e.newlineIndex(a+l)-a+1;e.formatAt(a,s,o,r[o])}else e.format(o,r[o]);l-=i})}}),this.scroll.optimize(),this.update((new l.default).retain(t).retain(e,(0,y.default)(r)))}},{key:"formatText",value:function(t,e){var n=this,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return Object.keys(r).forEach(function(o){n.scroll.formatAt(t,e,o,r[o])}),this.update((new l.default).retain(t).retain(e,(0,y.default)(r)))}},{key:"getContents",value:function(t,e){return this.delta.slice(t,t+e)}},{key:"getDelta",value:function(){return this.scroll.lines().reduce(function(t,e){return t.concat(e.delta())},new l.default)}},{key:"getFormat",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=[],r=[];0===e?this.scroll.path(t).forEach(function(t){var e=o(t,1)[0];e instanceof h.default?n.push(e):e instanceof u.default.Leaf&&r.push(e)}):(n=this.scroll.lines(t,e),r=this.scroll.descendants(u.default.Leaf,t,e));var i=[n,r].map(function(t){if(0===t.length)return{};for(var e=(0,d.bubbleFormats)(t.shift());Object.keys(e).length>0;){var n=t.shift();if(null==n)return e;e=w((0,d.bubbleFormats)(n),e)}return e});return b.default.apply(b.default,i)}},{key:"getText",value:function(t,e){return this.getContents(t,e).filter(function(t){return"string"==typeof t.insert}).map(function(t){return t.insert}).join("")}},{key:"insertEmbed",value:function(t,e,n){return this.scroll.insertAt(t,e,n),this.update((new l.default).retain(t).insert(function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}({},e,n)))}},{key:"insertText",value:function(t,e){var n=this,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e=e.replace(/\r\n/g,"\n").replace(/\r/g,"\n"),this.scroll.insertAt(t,e),Object.keys(r).forEach(function(o){n.scroll.formatAt(t,e.length,o,r[o])}),this.update((new l.default).retain(t).insert(e,(0,y.default)(r)))}},{key:"isBlank",value:function(){if(0==this.scroll.children.length)return!0;if(this.scroll.children.length>1)return!1;var t=this.scroll.children.head;return t.statics.blotName===h.default.blotName&&!(t.children.length>1)&&t.children.head instanceof p.default}},{key:"removeFormat",value:function(t,e){var n=this.getText(t,e),r=this.scroll.line(t+e),i=o(r,2),a=i[0],s=i[1],u=0,f=new l.default;null!=a&&(u=a instanceof c.default?a.newlineIndex(s)-s+1:a.length()-s,f=a.delta().slice(s,s+u-1).insert("\n"));var d=this.getContents(t,e+u).diff((new l.default).insert(n).concat(f)),h=(new l.default).retain(t).concat(d);return this.applyDelta(h)}},{key:"update",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,r=this.delta;if(1===e.length&&"characterData"===e[0].type&&e[0].target.data.match(m)&&u.default.find(e[0].target)){var o=u.default.find(e[0].target),i=(0,d.bubbleFormats)(o),a=o.offset(this.scroll),s=e[0].oldValue.replace(f.default.CONTENTS,""),c=(new l.default).insert(s),h=(new l.default).insert(o.value());t=(new l.default).retain(a).concat(c.diff(h,n)).reduce(function(t,e){return e.insert?t.insert(e.insert,i):t.push(e)},new l.default),this.delta=r.compose(t)}else this.delta=this.getDelta(),t&&(0,v.default)(r.compose(t),this.delta)||(t=r.diff(this.delta,n));return t}}]),t}();function w(t,e){return Object.keys(e).reduce(function(n,r){return null==t[r]?n:(e[r]===t[r]?n[r]=e[r]:Array.isArray(e[r])?e[r].indexOf(t[r])<0&&(n[r]=e[r].concat([t[r]])):n[r]=[e[r],t[r]],n)},{})}e.default=q},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.Range=void 0;var r=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},o=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:0;f(this,t),this.index=e,this.length=n},p=function(){function t(e,n){var r=this;f(this,t),this.emitter=n,this.scroll=e,this.composing=!1,this.mouseDown=!1,this.root=this.scroll.domNode,this.cursor=i.default.create("cursor",this),this.lastRange=this.savedRange=new h(0,0),this.handleComposition(),this.handleDragging(),this.emitter.listenDOM("selectionchange",document,function(){r.mouseDown||setTimeout(r.update.bind(r,s.default.sources.USER),1)}),this.emitter.on(s.default.events.EDITOR_CHANGE,function(t,e){t===s.default.events.TEXT_CHANGE&&e.length()>0&&r.update(s.default.sources.SILENT)}),this.emitter.on(s.default.events.SCROLL_BEFORE_UPDATE,function(){if(r.hasFocus()){var t=r.getNativeRange();null!=t&&t.start.node!==r.cursor.textNode&&r.emitter.once(s.default.events.SCROLL_UPDATE,function(){try{r.setNativeRange(t.start.node,t.start.offset,t.end.node,t.end.offset)}catch(t){}})}}),this.emitter.on(s.default.events.SCROLL_OPTIMIZE,function(t,e){if(e.range){var n=e.range,o=n.startNode,i=n.startOffset,l=n.endNode,a=n.endOffset;r.setNativeRange(o,i,l,a)}}),this.update(s.default.sources.SILENT)}return o(t,[{key:"handleComposition",value:function(){var t=this;this.root.addEventListener("compositionstart",function(){t.composing=!0}),this.root.addEventListener("compositionend",function(){if(t.composing=!1,t.cursor.parent){var e=t.cursor.restore();if(!e)return;setTimeout(function(){t.setNativeRange(e.startNode,e.startOffset,e.endNode,e.endOffset)},1)}})}},{key:"handleDragging",value:function(){var t=this;this.emitter.listenDOM("mousedown",document.body,function(){t.mouseDown=!0}),this.emitter.listenDOM("mouseup",document.body,function(){t.mouseDown=!1,t.update(s.default.sources.USER)})}},{key:"focus",value:function(){this.hasFocus()||(this.root.focus(),this.setRange(this.savedRange))}},{key:"format",value:function(t,e){if(null==this.scroll.whitelist||this.scroll.whitelist[t]){this.scroll.update();var n=this.getNativeRange();if(null!=n&&n.native.collapsed&&!i.default.query(t,i.default.Scope.BLOCK)){if(n.start.node!==this.cursor.textNode){var r=i.default.find(n.start.node,!1);if(null==r)return;if(r instanceof i.default.Leaf){var o=r.split(n.start.offset);r.parent.insertBefore(this.cursor,o)}else r.insertBefore(this.cursor,n.start.node);this.cursor.attach()}this.cursor.format(t,e),this.scroll.optimize(),this.setNativeRange(this.cursor.textNode,this.cursor.textNode.data.length),this.update()}}}},{key:"getBounds",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=this.scroll.length();t=Math.min(t,n-1),e=Math.min(t+e,n-1)-t;var o=void 0,i=this.scroll.leaf(t),l=r(i,2),a=l[0],s=l[1];if(null==a)return null;var u=a.position(s,!0),c=r(u,2);o=c[0],s=c[1];var f=document.createRange();if(e>0){f.setStart(o,s);var d=this.scroll.leaf(t+e),h=r(d,2);if(a=h[0],s=h[1],null==a)return null;var p=a.position(s,!0),y=r(p,2);return o=y[0],s=y[1],f.setEnd(o,s),f.getBoundingClientRect()}var v="left",b=void 0;return o instanceof Text?(s0&&(v="right")),{bottom:b.top+b.height,height:b.height,left:b[v],right:b[v],top:b.top,width:0}}},{key:"getNativeRange",value:function(){var t=document.getSelection();if(null==t||t.rangeCount<=0)return null;var e=t.getRangeAt(0);if(null==e)return null;var n=this.normalizeNative(e);return d.info("getNativeRange",n),n}},{key:"getRange",value:function(){var t=this.getNativeRange();return null==t?[null,null]:[this.normalizedToRange(t),t]}},{key:"hasFocus",value:function(){return document.activeElement===this.root}},{key:"normalizedToRange",value:function(t){var e=this,n=[[t.start.node,t.start.offset]];t.native.collapsed||n.push([t.end.node,t.end.offset]);var o=n.map(function(t){var n=r(t,2),o=n[0],l=n[1],a=i.default.find(o,!0),s=a.offset(e.scroll);return 0===l?s:a instanceof i.default.Container?s+a.length():s+a.index(o,l)}),l=Math.min(Math.max.apply(Math,c(o)),this.scroll.length()-1),a=Math.min.apply(Math,[l].concat(c(o)));return new h(a,l-a)}},{key:"normalizeNative",value:function(t){if(!y(this.root,t.startContainer)||!t.collapsed&&!y(this.root,t.endContainer))return null;var e={start:{node:t.startContainer,offset:t.startOffset},end:{node:t.endContainer,offset:t.endOffset},native:t};return[e.start,e.end].forEach(function(t){for(var e=t.node,n=t.offset;!(e instanceof Text)&&e.childNodes.length>0;)if(e.childNodes.length>n)e=e.childNodes[n],n=0;else{if(e.childNodes.length!==n)break;n=(e=e.lastChild)instanceof Text?e.data.length:e.childNodes.length+1}t.node=e,t.offset=n}),e}},{key:"rangeToNative",value:function(t){var e=this,n=t.collapsed?[t.index]:[t.index,t.index+t.length],o=[],i=this.scroll.length();return n.forEach(function(t,n){t=Math.min(i-1,t);var l,a=e.scroll.leaf(t),s=r(a,2),u=s[0],c=s[1],f=u.position(c,0!==n),d=r(f,2);l=d[0],c=d[1],o.push(l,c)}),o.length<2&&(o=o.concat(o)),o}},{key:"scrollIntoView",value:function(t){var e=this.lastRange;if(null!=e){var n=this.getBounds(e.index,e.length);if(null!=n){var o=this.scroll.length()-1,i=this.scroll.line(Math.min(e.index,o)),l=r(i,1)[0],a=l;if(e.length>0){var s=this.scroll.line(Math.min(e.index+e.length,o));a=r(s,1)[0]}if(null!=l&&null!=a){var u=t.getBoundingClientRect();n.topu.bottom&&(t.scrollTop+=n.bottom-u.bottom)}}}}},{key:"setNativeRange",value:function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:e,o=arguments.length>4&&void 0!==arguments[4]&&arguments[4];if(d.info("setNativeRange",t,e,n,r),null==t||null!=this.root.parentNode&&null!=t.parentNode&&null!=n.parentNode){var i=document.getSelection();if(null!=i)if(null!=t){this.hasFocus()||this.root.focus();var l=(this.getNativeRange()||{}).native;if(null==l||o||t!==l.startContainer||e!==l.startOffset||n!==l.endContainer||r!==l.endOffset){"BR"==t.tagName&&(e=[].indexOf.call(t.parentNode.childNodes,t),t=t.parentNode),"BR"==n.tagName&&(r=[].indexOf.call(n.parentNode.childNodes,n),n=n.parentNode);var a=document.createRange();a.setStart(t,e),a.setEnd(n,r),i.removeAllRanges(),i.addRange(a)}}else i.removeAllRanges(),this.root.blur(),document.body.focus()}}},{key:"setRange",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:s.default.sources.API;if("string"==typeof e&&(n=e,e=!1),d.info("setRange",t),null!=t){var r=this.rangeToNative(t);this.setNativeRange.apply(this,c(r).concat([e]))}else this.setNativeRange(null);this.update(n)}},{key:"update",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s.default.sources.USER,e=this.lastRange,n=this.getRange(),o=r(n,2),i=o[0],u=o[1];if(this.lastRange=i,null!=this.lastRange&&(this.savedRange=this.lastRange),!(0,a.default)(e,this.lastRange)){var c;!this.composing&&null!=u&&u.native.collapsed&&u.start.node!==this.cursor.textNode&&this.cursor.restore();var f,d=[s.default.events.SELECTION_CHANGE,(0,l.default)(this.lastRange),(0,l.default)(e),t];(c=this.emitter).emit.apply(c,[s.default.events.EDITOR_CHANGE].concat(d)),t!==s.default.sources.SILENT&&(f=this.emitter).emit.apply(f,d)}}}]),t}();function y(t,e){try{e.parentNode}catch(t){return!1}return e instanceof Text&&(e=e.parentNode),t.contains(e)}e.Range=h,e.default=p},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n0&&(n+=1),[this.parent.domNode,n]},e.prototype.value=function(){return(t={})[this.statics.blotName]=this.statics.value(this.domNode)||!0,t;var t},e.scope=l.Scope.INLINE_BLOT,e}(i.default);e.default=a},function(t,e,n){var r=n(11),o=n(3),i={attributes:{compose:function(t,e,n){"object"!==(void 0===t?"undefined":a(t))&&(t={}),"object"!==(void 0===e?"undefined":a(e))&&(e={});var r=o(!0,{},e);for(var i in n||(r=Object.keys(r).reduce(function(t,e){return null!=r[e]&&(t[e]=r[e]),t},{})),t)void 0!==t[i]&&void 0===e[i]&&(r[i]=t[i]);return Object.keys(r).length>0?r:void 0},diff:function(t,e){"object"!==(void 0===t?"undefined":a(t))&&(t={}),"object"!==(void 0===e?"undefined":a(e))&&(e={});var n=Object.keys(t).concat(Object.keys(e)).reduce(function(n,o){return r(t[o],e[o])||(n[o]=void 0===e[o]?null:e[o]),n},{});return Object.keys(n).length>0?n:void 0},transform:function(t,e,n){if("object"!==(void 0===t?"undefined":a(t)))return e;if("object"===(void 0===e?"undefined":a(e))){if(!n)return e;var r=Object.keys(e).reduce(function(n,r){return void 0===t[r]&&(n[r]=e[r]),n},{});return Object.keys(r).length>0?r:void 0}}},iterator:function(t){return new l(t)},length:function(t){return"number"==typeof t.delete?t.delete:"number"==typeof t.retain?t.retain:"string"==typeof t.insert?t.insert.length:1}};function l(t){this.ops=t,this.index=0,this.offset=0}l.prototype.hasNext=function(){return this.peekLength()<1/0},l.prototype.next=function(t){t||(t=1/0);var e=this.ops[this.index];if(e){var n=this.offset,r=i.length(e);if(t>=r-n?(t=r-n,this.index+=1,this.offset=0):this.offset+=t,"number"==typeof e.delete)return{delete:t};var o={};return e.attributes&&(o.attributes=e.attributes),"number"==typeof e.retain?o.retain=t:"string"==typeof e.insert?o.insert=e.insert.substr(n,t):o.insert=e.insert,o}return{retain:1/0}},l.prototype.peek=function(){return this.ops[this.index]},l.prototype.peekLength=function(){return this.ops[this.index]?i.length(this.ops[this.index])-this.offset:1/0},l.prototype.peekType=function(){return this.ops[this.index]?"number"==typeof this.ops[this.index].delete?"delete":"number"==typeof this.ops[this.index].retain?"retain":"insert":"retain"},t.exports=i},function(e,n){var r=function(){"use strict";function e(t,e){return null!=e&&t instanceof e}var n,r,o;try{n=Map}catch(t){n=function(){}}try{r=Set}catch(t){r=function(){}}try{o=Promise}catch(t){o=function(){}}function i(l,u,c,f,d){"object"===(void 0===u?"undefined":a(u))&&(c=u.depth,f=u.prototype,d=u.includeNonEnumerable,u=u.circular);var h=[],p=[],y=void 0!==t;return void 0===u&&(u=!0),void 0===c&&(c=1/0),function l(c,v){if(null===c)return null;if(0===v)return c;var b,g;if("object"!=(void 0===c?"undefined":a(c)))return c;if(e(c,n))b=new n;else if(e(c,r))b=new r;else if(e(c,o))b=new o(function(t,e){c.then(function(e){t(l(e,v-1))},function(t){e(l(t,v-1))})});else if(i.__isArray(c))b=[];else if(i.__isRegExp(c))b=new RegExp(c.source,s(c)),c.lastIndex&&(b.lastIndex=c.lastIndex);else if(i.__isDate(c))b=new Date(c.getTime());else{if(y&&t.isBuffer(c))return b=new t(c.length),c.copy(b),b;e(c,Error)?b=Object.create(c):void 0===f?(g=Object.getPrototypeOf(c),b=Object.create(g)):(b=Object.create(f),g=f)}if(u){var m=h.indexOf(c);if(-1!=m)return p[m];h.push(c),p.push(b)}for(var q in e(c,n)&&c.forEach(function(t,e){var n=l(e,v-1),r=l(t,v-1);b.set(n,r)}),e(c,r)&&c.forEach(function(t){var e=l(t,v-1);b.add(e)}),c){var w;g&&(w=Object.getOwnPropertyDescriptor(g,q)),w&&null==w.set||(b[q]=l(c[q],v-1))}if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(c);for(q=0;q<_.length;q++){var k=_[q];(!(x=Object.getOwnPropertyDescriptor(c,k))||x.enumerable||d)&&(b[k]=l(c[k],v-1),x.enumerable||Object.defineProperty(b,k,{enumerable:!1}))}}if(d){var O=Object.getOwnPropertyNames(c);for(q=0;q0){if(a instanceof u.BlockEmbed||h instanceof u.BlockEmbed)return void this.optimize();if(a instanceof d.default){var p=a.newlineIndex(a.length(),!0);if(p>-1&&(a=a.split(p+1))===h)return void this.optimize()}else if(h instanceof d.default){var y=h.newlineIndex(0);y>-1&&h.split(y+1)}var v=h.children.head instanceof f.default?null:h.children.head;a.moveChildren(h,v),a.remove()}this.optimize()}},{key:"enable",value:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.domNode.setAttribute("contenteditable",t)}},{key:"formatAt",value:function(t,n,r,o){(null==this.whitelist||this.whitelist[r])&&(i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"formatAt",this).call(this,t,n,r,o),this.optimize())}},{key:"insertAt",value:function(t,n,r){if(null==r||null==this.whitelist||this.whitelist[n]){if(t>=this.length())if(null==r||null==l.default.query(n,l.default.Scope.BLOCK)){var o=l.default.create(this.statics.defaultChild);this.appendChild(o),null==r&&n.endsWith("\n")&&(n=n.slice(0,-1)),o.insertAt(0,n,r)}else{var a=l.default.create(n,r);this.appendChild(a)}else i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"insertAt",this).call(this,t,n,r);this.optimize()}}},{key:"insertBefore",value:function(t,n){if(t.statics.scope===l.default.Scope.INLINE_BLOT){var r=l.default.create(this.statics.defaultChild);r.appendChild(t),t=r}i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"insertBefore",this).call(this,t,n)}},{key:"leaf",value:function(t){return this.path(t).pop()||[null,-1]}},{key:"line",value:function(t){return t===this.length()?this.line(t-1):this.descendant(y,t)}},{key:"lines",value:function(){return function t(e,n,r){var o=[],i=r;return e.children.forEachAt(n,r,function(e,n,r){y(e)?o.push(e):e instanceof l.default.Container&&(o=o.concat(t(e,n,i))),i-=r}),o}(this,arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,arguments.length>1&&void 0!==arguments[1]?arguments[1]:Number.MAX_VALUE)}},{key:"optimize",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};!0!==this.batch&&(i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"optimize",this).call(this,t,n),t.length>0&&this.emitter.emit(s.default.events.SCROLL_OPTIMIZE,t,n))}},{key:"path",value:function(t){return i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"path",this).call(this,t).slice(1)}},{key:"update",value:function(t){if(!0!==this.batch){var n=s.default.sources.USER;"string"==typeof t&&(n=t),Array.isArray(t)||(t=this.observer.takeRecords()),t.length>0&&this.emitter.emit(s.default.events.SCROLL_BEFORE_UPDATE,n,t),i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"update",this).call(this,t.concat([])),t.length>0&&this.emitter.emit(s.default.events.SCROLL_UPDATE,n,t)}}}]),e}();v.blotName="scroll",v.className="ql-editor",v.tagName="DIV",v.defaultChild="block",v.allowedChildren=[c.default,u.BlockEmbed,h.default],e.default=v},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.SHORTKEY=e.default=void 0;var r="function"==typeof Symbol&&"symbol"===a(Symbol.iterator)?function(t){return void 0===t?"undefined":a(t)}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":void 0===t?"undefined":a(t)},o=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},i=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=N(t);if(null==r||null==r.key)return g.warn("Attempted to add invalid keyboard binding",r);"function"==typeof e&&(e={handler:e}),"function"==typeof n&&(n={handler:n}),r=(0,u.default)(r,e,n),this.bindings[r.key]=this.bindings[r.key]||[],this.bindings[r.key].push(r)}},{key:"listen",value:function(){var t=this;this.quill.root.addEventListener("keydown",function(n){if(!n.defaultPrevented){var i=n.which||n.keyCode,l=(t.bindings[i]||[]).filter(function(t){return e.match(n,t)});if(0!==l.length){var a=t.quill.getSelection();if(null!=a&&t.quill.hasFocus()){var u=t.quill.getLine(a.index),c=o(u,2),f=c[0],h=c[1],p=t.quill.getLeaf(a.index),y=o(p,2),v=y[0],b=y[1],g=0===a.length?[v,b]:t.quill.getLeaf(a.index+a.length),m=o(g,2),q=m[0],w=m[1],_=v instanceof d.default.Text?v.value().slice(0,b):"",k=q instanceof d.default.Text?q.value().slice(w):"",O={collapsed:0===a.length,empty:0===a.length&&f.length()<=1,format:t.quill.getFormat(a),offset:h,prefix:_,suffix:k};l.some(function(e){if(null!=e.collapsed&&e.collapsed!==O.collapsed)return!1;if(null!=e.empty&&e.empty!==O.empty)return!1;if(null!=e.offset&&e.offset!==O.offset)return!1;if(Array.isArray(e.format)){if(e.format.every(function(t){return null==O.format[t]}))return!1}else if("object"===r(e.format)&&!Object.keys(e.format).every(function(t){return!0===e.format[t]?null!=O.format[t]:!1===e.format[t]?null==O.format[t]:(0,s.default)(e.format[t],O.format[t])}))return!1;return!(null!=e.prefix&&!e.prefix.test(O.prefix)||null!=e.suffix&&!e.suffix.test(O.suffix)||!0===e.handler.call(t,a,O))})&&n.preventDefault()}}}})}}]),e}();function w(t,e){var n,r=t===q.keys.LEFT?"prefix":"suffix";return b(n={key:t,shiftKey:e,altKey:null},r,/^$/),b(n,"handler",function(n){var r=n.index;t===q.keys.RIGHT&&(r+=n.length+1);var i=this.quill.getLeaf(r);return!(o(i,1)[0]instanceof d.default.Embed&&(t===q.keys.LEFT?e?this.quill.setSelection(n.index-1,n.length+1,h.default.sources.USER):this.quill.setSelection(n.index-1,h.default.sources.USER):e?this.quill.setSelection(n.index,n.length+1,h.default.sources.USER):this.quill.setSelection(n.index+n.length+1,h.default.sources.USER),1))}),n}function _(t,e){if(!(0===t.index||this.quill.getLength()<=1)){var n=this.quill.getLine(t.index),r=o(n,1)[0],i={};if(0===e.offset){var l=this.quill.getLine(t.index-1),a=o(l,1)[0];if(null!=a&&a.length()>1){var s=r.formats(),u=this.quill.getFormat(t.index-1,1);i=f.default.attributes.diff(s,u)||{}}}var c=/[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(e.prefix)?2:1;this.quill.deleteText(t.index-c,c,h.default.sources.USER),Object.keys(i).length>0&&this.quill.formatLine(t.index-c,c,i,h.default.sources.USER),this.quill.focus()}}function k(t,e){var n=/^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(e.suffix)?2:1;if(!(t.index>=this.quill.getLength()-n)){var r={},i=0,l=this.quill.getLine(t.index),a=o(l,1)[0];if(e.offset>=a.length()-1){var s=this.quill.getLine(t.index+1),u=o(s,1)[0];if(u){var c=a.formats(),d=this.quill.getFormat(t.index,1);r=f.default.attributes.diff(c,d)||{},i=u.length()}}this.quill.deleteText(t.index,n,h.default.sources.USER),Object.keys(r).length>0&&this.quill.formatLine(t.index+i-1,n,r,h.default.sources.USER)}}function O(t){var e=this.quill.getLines(t),n={};if(e.length>1){var r=e[0].formats(),o=e[e.length-1].formats();n=f.default.attributes.diff(o,r)||{}}this.quill.deleteText(t,h.default.sources.USER),Object.keys(n).length>0&&this.quill.formatLine(t.index,1,n,h.default.sources.USER),this.quill.setSelection(t.index,h.default.sources.SILENT),this.quill.focus()}function x(t,e){var n=this;t.length>0&&this.quill.scroll.deleteAt(t.index,t.length);var r=Object.keys(e.format).reduce(function(t,n){return d.default.query(n,d.default.Scope.BLOCK)&&!Array.isArray(e.format[n])&&(t[n]=e.format[n]),t},{});this.quill.insertText(t.index,"\n",r,h.default.sources.USER),this.quill.setSelection(t.index+1,h.default.sources.SILENT),this.quill.focus(),Object.keys(e.format).forEach(function(t){null==r[t]&&(Array.isArray(e.format[t])||"link"!==t&&n.quill.format(t,e.format[t],h.default.sources.USER))})}function E(t){return{key:q.keys.TAB,shiftKey:!t,format:{"code-block":!0},handler:function(e){var n=d.default.query("code-block"),r=e.index,i=e.length,l=this.quill.scroll.descendant(n,r),a=o(l,2),s=a[0],u=a[1];if(null!=s){var c=this.quill.getIndex(s),f=s.newlineIndex(u,!0)+1,p=s.newlineIndex(c+u+i),y=s.domNode.textContent.slice(f,p).split("\n");u=0,y.forEach(function(e,o){t?(s.insertAt(f+u,n.TAB),u+=n.TAB.length,0===o?r+=n.TAB.length:i+=n.TAB.length):e.startsWith(n.TAB)&&(s.deleteAt(f+u,n.TAB.length),u-=n.TAB.length,0===o?r-=n.TAB.length:i-=n.TAB.length),u+=e.length+1}),this.quill.update(h.default.sources.USER),this.quill.setSelection(r,i,h.default.sources.SILENT)}}}}function A(t){return{key:t[0].toUpperCase(),shortKey:!0,handler:function(e,n){this.quill.format(t,!n.format[t],h.default.sources.USER)}}}function N(t){if("string"==typeof t||"number"==typeof t)return N({key:t});if("object"===(void 0===t?"undefined":r(t))&&(t=(0,l.default)(t,!1)),"string"==typeof t.key)if(null!=q.keys[t.key.toUpperCase()])t.key=q.keys[t.key.toUpperCase()];else{if(1!==t.key.length)return null;t.key=t.key.toUpperCase().charCodeAt(0)}return t.shortKey&&(t[m]=t.shortKey,delete t.shortKey),t}q.keys={BACKSPACE:8,TAB:9,ENTER:13,ESCAPE:27,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46},q.DEFAULTS={bindings:{bold:A("bold"),italic:A("italic"),underline:A("underline"),indent:{key:q.keys.TAB,format:["blockquote","indent","list"],handler:function(t,e){if(e.collapsed&&0!==e.offset)return!0;this.quill.format("indent","+1",h.default.sources.USER)}},outdent:{key:q.keys.TAB,shiftKey:!0,format:["blockquote","indent","list"],handler:function(t,e){if(e.collapsed&&0!==e.offset)return!0;this.quill.format("indent","-1",h.default.sources.USER)}},"outdent backspace":{key:q.keys.BACKSPACE,collapsed:!0,shiftKey:null,metaKey:null,ctrlKey:null,altKey:null,format:["indent","list"],offset:0,handler:function(t,e){null!=e.format.indent?this.quill.format("indent","-1",h.default.sources.USER):null!=e.format.list&&this.quill.format("list",!1,h.default.sources.USER)}},"indent code-block":E(!0),"outdent code-block":E(!1),"remove tab":{key:q.keys.TAB,shiftKey:!0,collapsed:!0,prefix:/\t$/,handler:function(t){this.quill.deleteText(t.index-1,1,h.default.sources.USER)}},tab:{key:q.keys.TAB,handler:function(t){this.quill.history.cutoff();var e=(new c.default).retain(t.index).delete(t.length).insert("\t");this.quill.updateContents(e,h.default.sources.USER),this.quill.history.cutoff(),this.quill.setSelection(t.index+1,h.default.sources.SILENT)}},"list empty enter":{key:q.keys.ENTER,collapsed:!0,format:["list"],empty:!0,handler:function(t,e){this.quill.format("list",!1,h.default.sources.USER),e.format.indent&&this.quill.format("indent",!1,h.default.sources.USER)}},"checklist enter":{key:q.keys.ENTER,collapsed:!0,format:{list:"checked"},handler:function(t){var e=this.quill.getLine(t.index),n=o(e,2),r=n[0],i=n[1],l=(0,u.default)({},r.formats(),{list:"checked"}),a=(new c.default).retain(t.index).insert("\n",l).retain(r.length()-i-1).retain(1,{list:"unchecked"});this.quill.updateContents(a,h.default.sources.USER),this.quill.setSelection(t.index+1,h.default.sources.SILENT),this.quill.scrollIntoView()}},"header enter":{key:q.keys.ENTER,collapsed:!0,format:["header"],suffix:/^$/,handler:function(t,e){var n=this.quill.getLine(t.index),r=o(n,2),i=r[0],l=r[1],a=(new c.default).retain(t.index).insert("\n",e.format).retain(i.length()-l-1).retain(1,{header:null});this.quill.updateContents(a,h.default.sources.USER),this.quill.setSelection(t.index+1,h.default.sources.SILENT),this.quill.scrollIntoView()}},"list autofill":{key:" ",collapsed:!0,format:{list:!1},prefix:/^\s*?(\d+\.|-|\*|\[ ?\]|\[x\])$/,handler:function(t,e){var n=e.prefix.length,r=this.quill.getLine(t.index),i=o(r,2),l=i[0],a=i[1];if(a>n)return!0;var s=void 0;switch(e.prefix.trim()){case"[]":case"[ ]":s="unchecked";break;case"[x]":s="checked";break;case"-":case"*":s="bullet";break;default:s="ordered"}this.quill.insertText(t.index," ",h.default.sources.USER),this.quill.history.cutoff();var u=(new c.default).retain(t.index-a).delete(n+1).retain(l.length()-2-a).retain(1,{list:s});this.quill.updateContents(u,h.default.sources.USER),this.quill.history.cutoff(),this.quill.setSelection(t.index-n,h.default.sources.SILENT)}},"code exit":{key:q.keys.ENTER,collapsed:!0,format:["code-block"],prefix:/\n\n$/,suffix:/^\s+$/,handler:function(t){var e=this.quill.getLine(t.index),n=o(e,2),r=n[0],i=n[1],l=(new c.default).retain(t.index+r.length()-i-2).retain(1,{"code-block":null}).delete(1);this.quill.updateContents(l,h.default.sources.USER)}},"embed left":w(q.keys.LEFT,!1),"embed left shift":w(q.keys.LEFT,!0),"embed right":w(q.keys.RIGHT,!1),"embed right shift":w(q.keys.RIGHT,!0)}},e.default=q,e.SHORTKEY=m},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},o=function t(e,n,r){null===e&&(e=Function.prototype);var o=Object.getOwnPropertyDescriptor(e,n);if(void 0===o){var i=Object.getPrototypeOf(e);return null===i?void 0:t(i,n,r)}if("value"in o)return o.value;var l=o.get;return void 0!==l?l.call(r):void 0},i=function(){function t(t,e){for(var n=0;n-1}s.blotName="link",s.tagName="A",s.SANITIZED_URL="about:blank",s.PROTOCOL_WHITELIST=["http","https","mailto","tel"],e.default=s,e.sanitize=u},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r="function"==typeof Symbol&&"symbol"===a(Symbol.iterator)?function(t){return void 0===t?"undefined":a(t)}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":void 0===t?"undefined":a(t)},o=function(){function t(t,e){for(var n=0;n1&&void 0!==arguments[1]&&arguments[1],n=this.container.querySelector(".ql-selected");if(t!==n&&(null!=n&&n.classList.remove("ql-selected"),null!=t&&(t.classList.add("ql-selected"),this.select.selectedIndex=[].indexOf.call(t.parentNode.children,t),t.hasAttribute("data-value")?this.label.setAttribute("data-value",t.getAttribute("data-value")):this.label.removeAttribute("data-value"),t.hasAttribute("data-label")?this.label.setAttribute("data-label",t.getAttribute("data-label")):this.label.removeAttribute("data-label"),e))){if("function"==typeof Event)this.select.dispatchEvent(new Event("change"));else if("object"===("undefined"==typeof Event?"undefined":r(Event))){var o=document.createEvent("Event");o.initEvent("change",!0,!0),this.select.dispatchEvent(o)}this.close()}}},{key:"update",value:function(){var t=void 0;if(this.select.selectedIndex>-1){var e=this.container.querySelector(".ql-picker-options").children[this.select.selectedIndex];t=this.select.options[this.select.selectedIndex],this.selectItem(e)}else this.selectItem(null);var n=null!=t&&t!==this.select.querySelector("option[selected]");this.label.classList.toggle("ql-active",n)}}]),t}();e.default=f},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=b(n(0)),o=b(n(5)),i=n(4),l=b(i),a=b(n(16)),s=b(n(25)),u=b(n(24)),c=b(n(35)),f=b(n(6)),d=b(n(22)),h=b(n(7)),p=b(n(55)),y=b(n(42)),v=b(n(23));function b(t){return t&&t.__esModule?t:{default:t}}o.default.register({"blots/block":l.default,"blots/block/embed":i.BlockEmbed,"blots/break":a.default,"blots/container":s.default,"blots/cursor":u.default,"blots/embed":c.default,"blots/inline":f.default,"blots/scroll":d.default,"blots/text":h.default,"modules/clipboard":p.default,"modules/history":y.default,"modules/keyboard":v.default}),r.default.register(l.default,a.default,u.default,f.default,d.default,h.default),e.default=o.default},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(1),o=function(){function t(t){this.domNode=t,this.domNode[r.DATA_KEY]={blot:this}}return Object.defineProperty(t.prototype,"statics",{get:function(){return this.constructor},enumerable:!0,configurable:!0}),t.create=function(t){if(null==this.tagName)throw new r.ParchmentError("Blot definition missing tagName");var e;return Array.isArray(this.tagName)?("string"==typeof t&&(t=t.toUpperCase(),parseInt(t).toString()===t&&(t=parseInt(t))),e="number"==typeof t?document.createElement(this.tagName[t-1]):this.tagName.indexOf(t)>-1?document.createElement(t):document.createElement(this.tagName[0])):e=document.createElement(this.tagName),this.className&&e.classList.add(this.className),e},t.prototype.attach=function(){null!=this.parent&&(this.scroll=this.parent.scroll)},t.prototype.clone=function(){var t=this.domNode.cloneNode(!1);return r.create(t)},t.prototype.detach=function(){null!=this.parent&&this.parent.removeChild(this),delete this.domNode[r.DATA_KEY]},t.prototype.deleteAt=function(t,e){this.isolate(t,e).remove()},t.prototype.formatAt=function(t,e,n,o){var i=this.isolate(t,e);if(null!=r.query(n,r.Scope.BLOT)&&o)i.wrap(n,o);else if(null!=r.query(n,r.Scope.ATTRIBUTE)){var l=r.create(this.statics.scope);i.wrap(l),l.format(n,o)}},t.prototype.insertAt=function(t,e,n){var o=null==n?r.create("text",e):r.create(e,n),i=this.split(t);this.parent.insertBefore(o,i)},t.prototype.insertInto=function(t,e){void 0===e&&(e=null),null!=this.parent&&this.parent.children.remove(this);var n=null;t.children.insertBefore(this,e),null!=e&&(n=e.domNode),this.domNode.parentNode==t.domNode&&this.domNode.nextSibling==n||t.domNode.insertBefore(this.domNode,n),this.parent=t,this.attach()},t.prototype.isolate=function(t,e){var n=this.split(t);return n.split(e),n},t.prototype.length=function(){return 1},t.prototype.offset=function(t){return void 0===t&&(t=this.parent),null==this.parent||this==t?0:this.parent.children.offset(this)+this.parent.offset(t)},t.prototype.optimize=function(t){null!=this.domNode[r.DATA_KEY]&&delete this.domNode[r.DATA_KEY].mutations},t.prototype.remove=function(){null!=this.domNode.parentNode&&this.domNode.parentNode.removeChild(this.domNode),this.detach()},t.prototype.replace=function(t){null!=t.parent&&(t.parent.insertBefore(this,t.next),t.remove())},t.prototype.replaceWith=function(t,e){var n="string"==typeof t?r.create(t,e):t;return n.replace(this),n},t.prototype.split=function(t,e){return 0===t?this:this.next},t.prototype.update=function(t,e){},t.prototype.wrap=function(t,e){var n="string"==typeof t?r.create(t,e):t;return null!=this.parent&&this.parent.insertBefore(n,this.next),n.appendChild(this),n},t.blotName="abstract",t}();e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(12),o=n(32),i=n(33),l=n(1),a=function(){function t(t){this.attributes={},this.domNode=t,this.build()}return t.prototype.attribute=function(t,e){e?t.add(this.domNode,e)&&(null!=t.value(this.domNode)?this.attributes[t.attrName]=t:delete this.attributes[t.attrName]):(t.remove(this.domNode),delete this.attributes[t.attrName])},t.prototype.build=function(){var t=this;this.attributes={};var e=r.default.keys(this.domNode),n=o.default.keys(this.domNode),a=i.default.keys(this.domNode);e.concat(n).concat(a).forEach(function(e){var n=l.query(e,l.Scope.ATTRIBUTE);n instanceof r.default&&(t.attributes[n.attrName]=n)})},t.prototype.copy=function(t){var e=this;Object.keys(this.attributes).forEach(function(n){var r=e.attributes[n].value(e.domNode);t.format(n,r)})},t.prototype.move=function(t){var e=this;this.copy(t),Object.keys(this.attributes).forEach(function(t){e.attributes[t].remove(e.domNode)}),this.attributes={}},t.prototype.values=function(){var t=this;return Object.keys(this.attributes).reduce(function(e,n){return e[n]=t.attributes[n].value(t.domNode),e},{})},t}();e.default=a},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});function i(t,e){return(t.getAttribute("class")||"").split(/\s+/).filter(function(t){return 0===t.indexOf(e+"-")})}Object.defineProperty(e,"__esModule",{value:!0});var l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.keys=function(t){return(t.getAttribute("class")||"").split(/\s+/).map(function(t){return t.split("-").slice(0,-1).join("-")})},e.prototype.add=function(t,e){return!!this.canAdd(t,e)&&(this.remove(t),t.classList.add(this.keyName+"-"+e),!0)},e.prototype.remove=function(t){i(t,this.keyName).forEach(function(e){t.classList.remove(e)}),0===t.classList.length&&t.removeAttribute("class")},e.prototype.value=function(t){var e=(i(t,this.keyName)[0]||"").slice(this.keyName.length+1);return this.canAdd(t,e)?e:""},e}(n(12).default);e.default=l},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});function i(t){var e=t.split("-"),n=e.slice(1).map(function(t){return t[0].toUpperCase()+t.slice(1)}).join("");return e[0]+n}Object.defineProperty(e,"__esModule",{value:!0});var l=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.keys=function(t){return(t.getAttribute("style")||"").split(";").map(function(t){return t.split(":")[0].trim()})},e.prototype.add=function(t,e){return!!this.canAdd(t,e)&&(t.style[i(this.keyName)]=e,!0)},e.prototype.remove=function(t){t.style[i(this.keyName)]="",t.getAttribute("style")||t.removeAttribute("style")},e.prototype.value=function(t){var e=t.style[i(this.keyName)];return this.canAdd(t,e)?e:""},e}(n(12).default);e.default=l},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;nr&&this.stack.undo.length>0){var o=this.stack.undo.pop();n=n.compose(o.undo),t=o.redo.compose(t)}else this.lastRecorded=r;this.stack.undo.push({redo:t,undo:n}),this.stack.undo.length>this.options.maxStack&&this.stack.undo.shift()}}},{key:"redo",value:function(){this.change("redo","undo")}},{key:"transform",value:function(t){this.stack.undo.forEach(function(e){e.undo=t.transform(e.undo,!0),e.redo=t.transform(e.redo,!0)}),this.stack.redo.forEach(function(e){e.undo=t.transform(e.undo,!0),e.redo=t.transform(e.redo,!0)})}},{key:"undo",value:function(){this.change("undo","redo")}}]),e}();function c(t){var e=t.reduce(function(t,e){return t+(e.delete||0)},0),n=t.length()-e;return function(t){var e=t.ops[t.ops.length-1];return null!=e&&(null!=e.insert?"string"==typeof e.insert&&e.insert.endsWith("\n"):null!=e.attributes&&Object.keys(e.attributes).some(function(t){return null!=o.default.query(t,o.default.Scope.BLOCK)}))}(t)&&(n-=1),n}u.DEFAULTS={delay:1e3,maxStack:100,userOnly:!1},e.default=u,e.getLastChangeIndex=c},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.BaseTooltip=void 0;var r=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"link",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;this.root.classList.remove("ql-hidden"),this.root.classList.add("ql-editing"),null!=e?this.textbox.value=e:t!==this.root.getAttribute("data-mode")&&(this.textbox.value=""),this.position(this.quill.getBounds(this.quill.selection.savedRange)),this.textbox.select(),this.textbox.setAttribute("placeholder",this.textbox.getAttribute("data-"+t)||""),this.root.setAttribute("data-mode",t)}},{key:"restoreFocus",value:function(){var t=this.quill.scrollingContainer.scrollTop;this.quill.focus(),this.quill.scrollingContainer.scrollTop=t}},{key:"save",value:function(){var t=this.textbox.value;switch(this.root.getAttribute("data-mode")){case"link":var e=this.quill.root.scrollTop;this.linkRange?(this.quill.formatText(this.linkRange,"link",t,l.default.sources.USER),delete this.linkRange):(this.restoreFocus(),this.quill.format("link",t,l.default.sources.USER)),this.quill.root.scrollTop=e;break;case"video":t=function(t){var e=t.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtube\.com\/watch.*v=([a-zA-Z0-9_-]+)/)||t.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtu\.be\/([a-zA-Z0-9_-]+)/);return e?(e[1]||"https")+"://www.youtube.com/embed/"+e[2]+"?showinfo=0":(e=t.match(/^(?:(https?):\/\/)?(?:www\.)?vimeo\.com\/(\d+)/))?(e[1]||"https")+"://player.vimeo.com/video/"+e[2]+"/":t}(t);case"formula":if(!t)break;var n=this.quill.getSelection(!0);if(null!=n){var r=n.index+n.length;this.quill.insertEmbed(r,this.root.getAttribute("data-mode"),t,l.default.sources.USER),"formula"===this.root.getAttribute("data-mode")&&this.quill.insertText(r+1," ",l.default.sources.USER),this.quill.setSelection(r+2,l.default.sources.USER)}}this.textbox.value="",this.hide()}}]),e}();function x(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];e.forEach(function(e){var r=document.createElement("option");e===n?r.setAttribute("selected","selected"):r.setAttribute("value",e),t.appendChild(r)})}e.BaseTooltip=O,e.default=k},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(){this.head=this.tail=null,this.length=0}return t.prototype.append=function(){for(var t=[],e=0;e1&&this.append.apply(this,t.slice(1))},t.prototype.contains=function(t){for(var e,n=this.iterator();e=n();)if(e===t)return!0;return!1},t.prototype.insertBefore=function(t,e){t&&(t.next=e,null!=e?(t.prev=e.prev,null!=e.prev&&(e.prev.next=t),e.prev=t,e===this.head&&(this.head=t)):null!=this.tail?(this.tail.next=t,t.prev=this.tail,this.tail=t):(t.prev=null,this.head=this.tail=t),this.length+=1)},t.prototype.offset=function(t){for(var e=0,n=this.head;null!=n;){if(n===t)return e;e+=n.length(),n=n.next}return-1},t.prototype.remove=function(t){this.contains(t)&&(null!=t.prev&&(t.prev.next=t.next),null!=t.next&&(t.next.prev=t.prev),t===this.head&&(this.head=t.next),t===this.tail&&(this.tail=t.prev),this.length-=1)},t.prototype.iterator=function(t){return void 0===t&&(t=this.head),function(){var e=t;return null!=t&&(t=t.next),e}},t.prototype.find=function(t,e){void 0===e&&(e=!1);for(var n,r=this.iterator();n=r();){var o=n.length();if(tl?n(r,t-l,Math.min(e,l+s-t)):n(r,0,Math.min(s,t+e-l)),l+=s}},t.prototype.map=function(t){return this.reduce(function(e,n){return e.push(t(n)),e},[])},t.prototype.reduce=function(t,e){for(var n,r=this.iterator();n=r();)e=t(e,n);return e},t}();e.default=r},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0});var i=n(17),l=n(1),a={attributes:!0,characterData:!0,characterDataOldValue:!0,childList:!0,subtree:!0},s=function(t){function e(e){var n=t.call(this,e)||this;return n.scroll=n,n.observer=new MutationObserver(function(t){n.update(t)}),n.observer.observe(n.domNode,a),n.attach(),n}return o(e,t),e.prototype.detach=function(){t.prototype.detach.call(this),this.observer.disconnect()},e.prototype.deleteAt=function(e,n){this.update(),0===e&&n===this.length()?this.children.forEach(function(t){t.remove()}):t.prototype.deleteAt.call(this,e,n)},e.prototype.formatAt=function(e,n,r,o){this.update(),t.prototype.formatAt.call(this,e,n,r,o)},e.prototype.insertAt=function(e,n,r){this.update(),t.prototype.insertAt.call(this,e,n,r)},e.prototype.optimize=function(e,n){var r=this;void 0===e&&(e=[]),void 0===n&&(n={}),t.prototype.optimize.call(this,n);for(var o=[].slice.call(this.observer.takeRecords());o.length>0;)e.push(o.pop());for(var a=function t(e,n){void 0===n&&(n=!0),null!=e&&e!==r&&null!=e.domNode.parentNode&&(null==e.domNode[l.DATA_KEY].mutations&&(e.domNode[l.DATA_KEY].mutations=[]),n&&t(e.parent))},s=function t(e){null!=e.domNode[l.DATA_KEY]&&null!=e.domNode[l.DATA_KEY].mutations&&(e instanceof i.default&&e.children.forEach(t),e.optimize(n))},u=e,c=0;u.length>0;c+=1){if(c>=100)throw new Error("[Parchment] Maximum optimize iterations reached");for(u.forEach(function(t){var e=l.find(t.target,!0);null!=e&&(e.domNode===t.target&&("childList"===t.type?(a(l.find(t.previousSibling,!1)),[].forEach.call(t.addedNodes,function(t){var e=l.find(t,!1);a(e,!1),e instanceof i.default&&e.children.forEach(function(t){a(t,!1)})})):"attributes"===t.type&&a(e.prev)),a(e))}),this.children.forEach(s),o=(u=[].slice.call(this.observer.takeRecords())).slice();o.length>0;)e.push(o.pop())}},e.prototype.update=function(e,n){var r=this;void 0===n&&(n={}),(e=e||this.observer.takeRecords()).map(function(t){var e=l.find(t.target,!0);return null==e?null:null==e.domNode[l.DATA_KEY].mutations?(e.domNode[l.DATA_KEY].mutations=[t],e):(e.domNode[l.DATA_KEY].mutations.push(t),null)}).forEach(function(t){null!=t&&t!==r&&null!=t.domNode[l.DATA_KEY]&&t.update(t.domNode[l.DATA_KEY].mutations||[],n)}),null!=this.domNode[l.DATA_KEY].mutations&&t.prototype.update.call(this,this.domNode[l.DATA_KEY].mutations,n),this.optimize(e,n)},e.blotName="scroll",e.defaultChild="block",e.scope=l.Scope.BLOCK_BLOT,e.tagName="DIV",e}(i.default);e.default=s},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0});var i=n(18),l=n(1),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.formats=function(n){if(n.tagName!==e.tagName)return t.formats.call(this,n)},e.prototype.format=function(n,r){var o=this;n!==this.statics.blotName||r?t.prototype.format.call(this,n,r):(this.children.forEach(function(t){t instanceof i.default||(t=t.wrap(e.blotName,!0)),o.attributes.copy(t)}),this.unwrap())},e.prototype.formatAt=function(e,n,r,o){null!=this.formats()[r]||l.query(r,l.Scope.ATTRIBUTE)?this.isolate(e,n).format(r,o):t.prototype.formatAt.call(this,e,n,r,o)},e.prototype.optimize=function(n){t.prototype.optimize.call(this,n);var r=this.formats();if(0===Object.keys(r).length)return this.unwrap();var o=this.next;o instanceof e&&o.prev===this&&function(t,e){if(Object.keys(t).length!==Object.keys(e).length)return!1;for(var n in t)if(t[n]!==e[n])return!1;return!0}(r,o.formats())&&(o.moveChildren(this),o.remove())},e.blotName="inline",e.scope=l.Scope.INLINE_BLOT,e.tagName="SPAN",e}(i.default);e.default=a},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0});var i=n(18),l=n(1),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.formats=function(n){var r=l.query(e.blotName).tagName;if(n.tagName!==r)return t.formats.call(this,n)},e.prototype.format=function(n,r){null!=l.query(n,l.Scope.BLOCK)&&(n!==this.statics.blotName||r?t.prototype.format.call(this,n,r):this.replaceWith(e.blotName))},e.prototype.formatAt=function(e,n,r,o){null!=l.query(r,l.Scope.BLOCK)?this.format(r,o):t.prototype.formatAt.call(this,e,n,r,o)},e.prototype.insertAt=function(e,n,r){if(null==r||null!=l.query(n,l.Scope.INLINE))t.prototype.insertAt.call(this,e,n,r);else{var o=this.split(e),i=l.create(n,r);o.parent.insertBefore(i,o)}},e.prototype.update=function(e,n){navigator.userAgent.match(/Trident/)?this.build():t.prototype.update.call(this,e,n)},e.blotName="block",e.scope=l.Scope.BLOCK_BLOT,e.tagName="P",e}(i.default);e.default=a},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0});var i=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return o(e,t),e.formats=function(t){},e.prototype.format=function(e,n){t.prototype.formatAt.call(this,0,this.length(),e,n)},e.prototype.formatAt=function(e,n,r,o){0===e&&n===this.length()?this.format(r,o):t.prototype.formatAt.call(this,e,n,r,o)},e.prototype.formats=function(){return this.statics.formats(this.domNode)},e}(n(19).default);e.default=i},function(t,e,n){"use strict";var r,o=this&&this.__extends||(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},function(t,e){function n(){this.constructor=t}r(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0});var i=n(19),l=n(1),a=function(t){function e(e){var n=t.call(this,e)||this;return n.text=n.statics.value(n.domNode),n}return o(e,t),e.create=function(t){return document.createTextNode(t)},e.value=function(t){var e=t.data;return e.normalize&&(e=e.normalize()),e},e.prototype.deleteAt=function(t,e){this.domNode.data=this.text=this.text.slice(0,t)+this.text.slice(t+e)},e.prototype.index=function(t,e){return this.domNode===t?e:-1},e.prototype.insertAt=function(e,n,r){null==r?(this.text=this.text.slice(0,e)+n+this.text.slice(e),this.domNode.data=this.text):t.prototype.insertAt.call(this,e,n,r)},e.prototype.length=function(){return this.text.length},e.prototype.optimize=function(n){t.prototype.optimize.call(this,n),this.text=this.statics.value(this.domNode),0===this.text.length?this.remove():this.next instanceof e&&this.next.prev===this&&(this.insertAt(this.length(),this.next.value()),this.next.remove())},e.prototype.position=function(t,e){return void 0===e&&(e=!1),[this.domNode,t]},e.prototype.split=function(t,e){if(void 0===e&&(e=!1),!e){if(0===t)return this;if(t===this.length())return this.next}var n=l.create(this.domNode.splitText(t));return this.parent.insertBefore(n,this.next),this.text=this.statics.value(this.domNode),n},e.prototype.update=function(t,e){var n=this;t.some(function(t){return"characterData"===t.type&&t.target===n.domNode})&&(this.text=this.statics.value(this.domNode))},e.prototype.value=function(){return this.text},e.blotName="text",e.scope=l.Scope.INLINE_BLOT,e}(i.default);e.default=a},function(t,e,n){"use strict";var r=document.createElement("div");if(r.classList.toggle("test-class",!1),r.classList.contains("test-class")){var o=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return arguments.length>1&&!this.contains(t)==!e?e:o.call(this,t)}}String.prototype.startsWith||(String.prototype.startsWith=function(t,e){return e=e||0,this.substr(e,t.length)===t}),String.prototype.endsWith||(String.prototype.endsWith=function(t,e){var n=this.toString();("number"!=typeof e||!isFinite(e)||Math.floor(e)!==e||e>n.length)&&(e=n.length),e-=t.length;var r=n.indexOf(t,e);return-1!==r&&r===e}),Array.prototype.find||Object.defineProperty(Array.prototype,"find",{value:function(t){if(null===this)throw new TypeError("Array.prototype.find called on null or undefined");if("function"!=typeof t)throw new TypeError("predicate must be a function");for(var e,n=Object(this),r=n.length>>>0,o=arguments[1],i=0;ie.length?t:e,f=t.length>e.length?e:t,d=c.indexOf(f);if(-1!=d)return u=[[r,c.substring(0,d)],[o,f],[r,c.substring(d+f.length)]],t.length>e.length&&(u[0][0]=u[2][0]=n),u;if(1==f.length)return[[n,t],[r,e]];var h=function(t,e){var n=t.length>e.length?t:e,r=t.length>e.length?e:t;if(n.length<4||2*r.length=t.length?[r,o,i,l,f]:null}var i,l,u,c,f,d=o(n,r,Math.ceil(n.length/4)),h=o(n,r,Math.ceil(n.length/2));return d||h?(i=h?d&&d[4].length>h[4].length?d:h:d,t.length>e.length?(l=i[0],u=i[1],c=i[2],f=i[3]):(c=i[0],f=i[1],l=i[2],u=i[3]),[l,u,c,f,i[4]]):null}(t,e);if(h){var p=h[0],y=h[1],v=h[2],b=h[3],g=h[4],m=i(p,v),q=i(y,b);return m.concat([[o,g]],q)}return function(t,e){for(var o=t.length,i=e.length,a=Math.ceil((o+i)/2),s=a,u=2*a,c=new Array(u),f=new Array(u),d=0;do)v+=2;else if(_>i)y+=2;else if(p){if((x=s+h-q)>=0&&x=k)return l(t,e,A,_)}}}for(var O=-m+b;O<=m-g;O+=2){for(var x=s+O,E=(k=O==-m||O!=m&&f[x-1]o)g+=2;else if(E>i)b+=2;else if(!p){if((w=s+h-O)>=0&&w=(k=o-k))return l(t,e,A,_)}}}}return[[n,t],[r,e]]}(t,e)}(t=t.substring(0,t.length-f),e=e.substring(0,e.length-f));return d&&p.unshift([o,d]),h&&p.push([o,h]),function t(e){e.push([o,""]);for(var i,l=0,u=0,c=0,f="",d="";l1?(0!==u&&0!==c&&(0!==(i=a(d,f))&&(l-u-c>0&&e[l-u-c-1][0]==o?e[l-u-c-1][1]+=d.substring(0,i):(e.splice(0,0,[o,d.substring(0,i)]),l++),d=d.substring(i),f=f.substring(i)),0!==(i=s(d,f))&&(e[l][1]=d.substring(d.length-i)+e[l][1],d=d.substring(0,d.length-i),f=f.substring(0,f.length-i))),0===u?e.splice(l-c,u+c,[r,d]):0===c?e.splice(l-u,u+c,[n,f]):e.splice(l-u-c,u+c,[n,f],[r,d]),l=l-u-c+(u?1:0)+(c?1:0)+1):0!==l&&e[l-1][0]==o?(e[l-1][1]+=e[l][1],e.splice(l,1)):l++,c=0,u=0,f="",d=""}""===e[e.length-1][1]&&e.pop();var h=!1;for(l=1;l0&&i.splice(l+2,0,[s[0],u]),c(i,l,3)}return t}(p,u)),function(t){for(var e=!1,i=function(t){return t.charCodeAt(0)>=56320&&t.charCodeAt(0)<=57343},l=function(t){return t.charCodeAt(t.length-1)>=55296&&t.charCodeAt(t.length-1)<=56319},a=2;a0&&s.push(t[a]);return s}(p)}function l(t,e,n,r){var o=t.substring(0,n),l=e.substring(0,r),a=t.substring(n),s=e.substring(r),u=i(o,l),c=i(a,s);return u.concat(c)}function a(t,e){if(!t||!e||t.charAt(0)!=e.charAt(0))return 0;for(var n=0,r=Math.min(t.length,e.length),o=r,i=0;n=0&&r>=e-1;r--)if(r+1=700)&&(n.bold=!0),Object.keys(n).length>0&&(e=N(e,n)),parseFloat(r.textIndent||0)>0&&(e=(new s.default).insert("\t").concat(e)),e}],["li",function(t,e){var n=u.default.query(t);if(null==n||"list-item"!==n.blotName||!T(e,"\n"))return e;for(var r=-1,o=t.parentNode;!o.classList.contains("ql-clipboard");)"list"===(u.default.query(o)||{}).blotName&&(r+=1),o=o.parentNode;return r<=0?e:e.compose((new s.default).retain(e.length()-1).retain(1,{indent:r}))}],["b",P.bind(P,"bold")],["i",P.bind(P,"italic")],["style",function(){return new s.default}]],x=[h.AlignAttribute,b.DirectionAttribute].reduce(function(t,e){return t[e.keyName]=e,t},{}),E=[h.AlignStyle,p.BackgroundStyle,v.ColorStyle,b.DirectionStyle,g.FontStyle,m.SizeStyle].reduce(function(t,e){return t[e.keyName]=e,t},{}),A=function(t){function e(t,n){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e);var r=function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!==(void 0===e?"undefined":a(e))&&"function"!=typeof e?t:e}(this,(e.__proto__||Object.getPrototypeOf(e)).call(this,t,n));return r.quill.root.addEventListener("paste",r.onPaste.bind(r)),r.container=r.quill.addContainer("ql-clipboard"),r.container.setAttribute("contenteditable",!0),r.container.setAttribute("tabindex",-1),r.matchers=[],O.concat(r.options.matchers).forEach(function(t){var e=o(t,2),i=e[0],l=e[1];(n.matchVisual||l!==M)&&r.addMatcher(i,l)}),r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+(void 0===e?"undefined":a(e)));t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,d.default),i(e,[{key:"addMatcher",value:function(t,e){this.matchers.push([t,e])}},{key:"convert",value:function(t){if("string"==typeof t)return this.container.innerHTML=t.replace(/\>\r?\n +\<"),this.convert();var e=this.quill.getFormat(this.quill.selection.savedRange.index);if(e[y.default.blotName]){var n=this.container.innerText;return this.container.innerHTML="",(new s.default).insert(n,w({},y.default.blotName,e[y.default.blotName]))}var r=this.prepareMatching(),i=o(r,2),l=i[0],a=i[1],u=function t(e,n,r){return e.nodeType===e.TEXT_NODE?r.reduce(function(t,n){return n(e,t)},new s.default):e.nodeType===e.ELEMENT_NODE?[].reduce.call(e.childNodes||[],function(o,i){var l=t(i,n,r);return i.nodeType===e.ELEMENT_NODE&&(l=n.reduce(function(t,e){return e(i,t)},l),l=(i[k]||[]).reduce(function(t,e){return e(i,t)},l)),o.concat(l)},new s.default):new s.default}(this.container,l,a);return T(u,"\n")&&null==u.ops[u.ops.length-1].attributes&&(u=u.compose((new s.default).retain(u.length()-1).delete(1))),_.log("convert",this.container.innerHTML,u),this.container.innerHTML="",u}},{key:"dangerouslyPasteHTML",value:function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:c.default.sources.API;if("string"==typeof t)this.quill.setContents(this.convert(t),e),this.quill.setSelection(0,c.default.sources.SILENT);else{var r=this.convert(e);this.quill.updateContents((new s.default).retain(t).concat(r),n),this.quill.setSelection(t+r.length(),c.default.sources.SILENT)}}},{key:"onPaste",value:function(t){var e=this;if(!t.defaultPrevented&&this.quill.isEnabled()){var n=this.quill.getSelection(),r=(new s.default).retain(n.index),o=this.quill.scrollingContainer.scrollTop;this.container.focus(),this.quill.selection.update(c.default.sources.SILENT),setTimeout(function(){r=r.concat(e.convert()).delete(n.length),e.quill.updateContents(r,c.default.sources.USER),e.quill.setSelection(r.length()-n.length,c.default.sources.SILENT),e.quill.scrollingContainer.scrollTop=o,e.quill.focus()},1)}}},{key:"prepareMatching",value:function(){var t=this,e=[],n=[];return this.matchers.forEach(function(r){var i=o(r,2),l=i[0],a=i[1];switch(l){case Node.TEXT_NODE:n.push(a);break;case Node.ELEMENT_NODE:e.push(a);break;default:[].forEach.call(t.container.querySelectorAll(l),function(t){t[k]=t[k]||[],t[k].push(a)})}}),[e,n]}}]),e}();function N(t,e,n){return"object"===(void 0===e?"undefined":r(e))?Object.keys(e).reduce(function(t,n){return N(t,n,e[n])},t):t.reduce(function(t,r){return r.attributes&&r.attributes[e]?t.push(r):t.insert(r.insert,(0,l.default)({},w({},e,n),r.attributes))},new s.default)}function j(t){return t.nodeType!==Node.ELEMENT_NODE?{}:t["__ql-computed-style"]||(t["__ql-computed-style"]=window.getComputedStyle(t))}function T(t,e){for(var n="",r=t.ops.length-1;r>=0&&n.length-1}function P(t,e,n){return N(n,t,!0)}function C(t,e){var n=u.default.Attributor.Attribute.keys(t),r=u.default.Attributor.Class.keys(t),o=u.default.Attributor.Style.keys(t),i={};return n.concat(r).concat(o).forEach(function(e){var n=u.default.query(e,u.default.Scope.ATTRIBUTE);null!=n&&(i[n.attrName]=n.value(t),i[n.attrName])||(null==(n=x[e])||n.attrName!==e&&n.keyName!==e||(i[n.attrName]=n.value(t)||void 0),null==(n=E[e])||n.attrName!==e&&n.keyName!==e||(n=E[e],i[n.attrName]=n.value(t)||void 0))}),Object.keys(i).length>0&&(e=N(e,i)),e}function L(t,e){var n=u.default.query(t);if(null==n)return e;if(n.prototype instanceof u.default.Embed){var r={},o=n.value(t);null!=o&&(r[n.blotName]=o,e=(new s.default).insert(r,n.formats(t)))}else"function"==typeof n.formats&&(e=N(e,n.blotName,n.formats(t)));return e}function R(t,e){return T(e,"\n")||(S(t)||e.length()>0&&t.nextSibling&&S(t.nextSibling))&&e.insert("\n"),e}function M(t,e){if(S(t)&&null!=t.nextElementSibling&&!T(e,"\n\n")){var n=t.offsetHeight+parseFloat(j(t).marginTop)+parseFloat(j(t).marginBottom);t.nextElementSibling.offsetTop>t.offsetTop+1.5*n&&e.insert("\n")}return e}function I(t,e){var n=t.data;if("O:P"===t.parentNode.tagName)return e.insert(n.trim());if(0===n.trim().length&&t.parentNode.classList.contains("ql-clipboard"))return e;if(!j(t.parentNode).whiteSpace.startsWith("pre")){var r=function(t,e){return(e=e.replace(/[^\u00a0]/g,"")).length<1&&t?" ":e};n=(n=n.replace(/\r\n/g," ").replace(/\n/g," ")).replace(/\s\s+/g,r.bind(r,!0)),(null==t.previousSibling&&S(t.parentNode)||null!=t.previousSibling&&S(t.previousSibling))&&(n=n.replace(/^\s+/,r.bind(r,!1))),(null==t.nextSibling&&S(t.parentNode)||null!=t.nextSibling&&S(t.nextSibling))&&(n=n.replace(/\s+$/,r.bind(r,!1)))}return e.insert(n)}A.DEFAULTS={matchers:[],matchVisual:!0},e.default=A,e.matchAttributor=C,e.matchBlot=L,e.matchNewline=R,e.matchSpacing=M,e.matchText=I},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n '},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;nr.right&&(i=r.right-o.right,this.root.style.left=e+i+"px"),o.leftr.bottom){var l=o.bottom-o.top,a=t.bottom-t.top+l;this.root.style.top=n-a+"px",this.root.classList.add("ql-flip")}return i}},{key:"show",value:function(){this.root.classList.remove("ql-editing"),this.root.classList.remove("ql-hidden")}}]),t}();e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var l,a=t[Symbol.iterator]();!(r=(l=a.next()).done)&&(n.push(l.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}return n}(t,e);throw new TypeError("Invalid attempt to destructure non-iterable instance")},o=function t(e,n,r){null===e&&(e=Function.prototype);var o=Object.getOwnPropertyDescriptor(e,n);if(void 0===o){var i=Object.getPrototypeOf(e);return null===i?void 0:t(i,n,r)}if("value"in o)return o.value;var l=o.get;return void 0!==l?l.call(r):void 0},i=function(){function t(t,e){for(var n=0;n','','',''].join(""),e.default=m},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=R(n(29)),o=n(36),i=n(38),l=n(64),a=R(n(65)),s=R(n(66)),u=n(67),c=R(u),f=n(37),d=n(26),h=n(39),p=n(40),y=R(n(56)),v=R(n(68)),b=R(n(27)),g=R(n(69)),m=R(n(70)),q=R(n(71)),w=R(n(72)),_=R(n(73)),k=n(13),O=R(k),x=R(n(74)),E=R(n(75)),A=R(n(57)),N=R(n(41)),j=R(n(28)),T=R(n(59)),S=R(n(60)),P=R(n(61)),C=R(n(108)),L=R(n(62));function R(t){return t&&t.__esModule?t:{default:t}}r.default.register({"attributors/attribute/direction":i.DirectionAttribute,"attributors/class/align":o.AlignClass,"attributors/class/background":f.BackgroundClass,"attributors/class/color":d.ColorClass,"attributors/class/direction":i.DirectionClass,"attributors/class/font":h.FontClass,"attributors/class/size":p.SizeClass,"attributors/style/align":o.AlignStyle,"attributors/style/background":f.BackgroundStyle,"attributors/style/color":d.ColorStyle,"attributors/style/direction":i.DirectionStyle,"attributors/style/font":h.FontStyle,"attributors/style/size":p.SizeStyle},!0),r.default.register({"formats/align":o.AlignClass,"formats/direction":i.DirectionClass,"formats/indent":l.IndentClass,"formats/background":f.BackgroundStyle,"formats/color":d.ColorStyle,"formats/font":h.FontClass,"formats/size":p.SizeClass,"formats/blockquote":a.default,"formats/code-block":O.default,"formats/header":s.default,"formats/list":c.default,"formats/bold":y.default,"formats/code":k.Code,"formats/italic":v.default,"formats/link":b.default,"formats/script":g.default,"formats/strike":m.default,"formats/underline":q.default,"formats/image":w.default,"formats/video":_.default,"formats/list/item":u.ListItem,"modules/formula":x.default,"modules/syntax":E.default,"modules/toolbar":A.default,"themes/bubble":C.default,"themes/snow":L.default,"ui/icons":N.default,"ui/picker":j.default,"ui/icon-picker":S.default,"ui/color-picker":T.default,"ui/tooltip":P.default},!0),e.default=r.default},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.IndentClass=void 0;var r,o=function(){function t(t,e){for(var n=0;n0&&this.children.tail.format(t,e)}},{key:"formats",value:function(){return t={},e=this.statics.blotName,n=this.statics.formats(this.domNode),e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t;var t,e,n}},{key:"insertBefore",value:function(t,n){if(t instanceof h)o(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"insertBefore",this).call(this,t,n);else{var r=null==n?this.length():n.offset(this),i=this.split(r);i.parent.insertBefore(t,i)}}},{key:"optimize",value:function(t){o(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"optimize",this).call(this,t);var n=this.next;null!=n&&n.prev===this&&n.statics.blotName===this.statics.blotName&&n.domNode.tagName===this.domNode.tagName&&n.domNode.getAttribute("data-checked")===this.domNode.getAttribute("data-checked")&&(n.moveChildren(this),n.remove())}},{key:"replace",value:function(t){if(t.statics.blotName!==this.statics.blotName){var n=i.default.create(this.statics.defaultChild);t.moveChildren(n),this.appendChild(n)}o(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"replace",this).call(this,t)}}]),e}();p.blotName="list",p.scope=i.default.Scope.BLOCK_BLOT,p.tagName=["OL","UL"],p.defaultChild="list-item",p.allowedChildren=[h],e.ListItem=h,e.default=p},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=(r=n(56))&&r.__esModule?r:{default:r},i=function(t){function e(){return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!==(void 0===e?"undefined":a(e))&&"function"!=typeof e?t:e}(this,(e.__proto__||Object.getPrototypeOf(e)).apply(this,arguments))}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+(void 0===e?"undefined":a(e)));t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,o.default),e}();i.blotName="italic",i.tagName=["EM","I"],e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n-1?n?this.domNode.setAttribute(t,n):this.domNode.removeAttribute(t):i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"format",this).call(this,t,n)}}],[{key:"create",value:function(t){var n=i(e.__proto__||Object.getPrototypeOf(e),"create",this).call(this,t);return"string"==typeof t&&n.setAttribute("src",this.sanitize(t)),n}},{key:"formats",value:function(t){return u.reduce(function(e,n){return t.hasAttribute(n)&&(e[n]=t.getAttribute(n)),e},{})}},{key:"match",value:function(t){return/\.(jpe?g|gif|png)$/.test(t)||/^data:image\/.+;base64/.test(t)}},{key:"sanitize",value:function(t){return(0,s.sanitize)(t,["http","https","data"])?t:"//:0"}},{key:"value",value:function(t){return t.getAttribute("src")}}]),e}();c.blotName="image",c.tagName="IMG",e.default=c},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n-1?n?this.domNode.setAttribute(t,n):this.domNode.removeAttribute(t):i(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"format",this).call(this,t,n)}}],[{key:"create",value:function(t){var n=i(e.__proto__||Object.getPrototypeOf(e),"create",this).call(this,t);return n.setAttribute("frameborder","0"),n.setAttribute("allowfullscreen",!0),n.setAttribute("src",this.sanitize(t)),n}},{key:"formats",value:function(t){return u.reduce(function(e,n){return t.hasAttribute(n)&&(e[n]=t.getAttribute(n)),e},{})}},{key:"sanitize",value:function(t){return s.default.sanitize(t)}},{key:"value",value:function(t){return t.getAttribute("src")}}]),e}();c.blotName="video",c.className="ql-video",c.tagName="IFRAME",e.default=c},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.FormulaBlot=void 0;var r=function(){function t(t,e){for(var n=0;n0||null==this.cachedText)&&(this.domNode.innerHTML=t(e),this.domNode.normalize(),this.attach()),this.cachedText=e)}}]),e}();h.className="ql-syntax";var p=new o.default.Attributor.Class("token","hljs",{scope:o.default.Scope.INLINE}),y=function(t){function e(t,n){c(this,e);var r=f(this,(e.__proto__||Object.getPrototypeOf(e)).call(this,t,n));if("function"!=typeof r.options.highlight)throw new Error("Syntax module requires highlight.js. Please include the library on the page before Quill.");var o=null;return r.quill.on(i.default.events.SCROLL_OPTIMIZE,function(){clearTimeout(o),o=setTimeout(function(){r.highlight(),o=null},r.options.interval)}),r.highlight(),r}return d(e,l.default),r(e,null,[{key:"register",value:function(){i.default.register(p,!0),i.default.register(h,!0)}}]),r(e,[{key:"highlight",value:function(){var t=this;if(!this.quill.selection.composing){this.quill.update(i.default.sources.USER);var e=this.quill.getSelection();this.quill.scroll.descendants(h).forEach(function(e){e.highlight(t.options.highlight)}),this.quill.update(i.default.sources.SILENT),null!=e&&this.quill.setSelection(e,i.default.sources.SILENT)}}}]),e}();y.DEFAULTS={highlight:null==window.hljs?null:function(t){return window.hljs.highlightAuto(t).value},interval:1e3},e.CodeBlock=h,e.CodeToken=p,e.default=y},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e){t.exports=' '},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.BubbleTooltip=void 0;var r=function t(e,n,r){null===e&&(e=Function.prototype);var o=Object.getOwnPropertyDescriptor(e,n);if(void 0===o){var i=Object.getPrototypeOf(e);return null===i?void 0:t(i,n,r)}if("value"in o)return o.value;var l=o.get;return void 0!==l?l.call(r):void 0},o=function(){function t(t,e){for(var n=0;n0&&o===l.default.sources.USER){r.show(),r.root.style.left="0px",r.root.style.width="",r.root.style.width=r.root.offsetWidth+"px";var i=r.quill.getLines(e.index,e.length);if(1===i.length)r.position(r.quill.getBounds(e));else{var a=i[i.length-1],s=r.quill.getIndex(a),u=Math.min(a.length()-1,e.index+e.length-s),f=r.quill.getBounds(new c.Range(s,u));r.position(f)}}else document.activeElement!==r.textbox&&r.quill.hasFocus()&&r.hide()}),r}return y(e,s.BaseTooltip),o(e,[{key:"listen",value:function(){var t=this;r(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"listen",this).call(this),this.root.querySelector(".ql-close").addEventListener("click",function(){t.root.classList.remove("ql-editing")}),this.quill.on(l.default.events.SCROLL_OPTIMIZE,function(){setTimeout(function(){if(!t.root.classList.contains("ql-hidden")){var e=t.quill.getSelection();null!=e&&t.position(t.quill.getBounds(e))}},1)})}},{key:"cancel",value:function(){this.show()}},{key:"position",value:function(t){var n=r(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"position",this).call(this,t),o=this.root.querySelector(".ql-tooltip-arrow");if(o.style.marginLeft="",0===n)return n;o.style.marginLeft=-1*n-o.offsetWidth/2+"px"}}]),e}();g.TEMPLATE=['','
    ','','',"
    "].join(""),e.BubbleTooltip=g,e.default=b},function(t,e,n){t.exports=n(63)}]).default},"object"===a(e)&&"object"===a(n)?n.exports=l():(o=[],void 0===(i="function"==typeof(r=l)?r.apply(e,o):r)||(n.exports=i))}).call(this,n(101).Buffer,n(11)(t))},3:function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=function(t,e){var n,r=t[1]||"",o=t[3];if(!o)return r;if(e&&"function"==typeof btoa){var i=(n=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),l=o.sources.map(function(t){return"/*# sourceURL="+o.sourceRoot+t+" */"});return[r].concat(l).concat([i]).join("\n")}return[r].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},o=0;o=0&&c.splice(e,1)}function v(t){var e=document.createElement("style");return t.attrs.type="text/css",b(e,t.attrs),p(t,e),e}function b(t,e){Object.keys(e).forEach(function(n){t.setAttribute(n,e[n])})}function g(t,e){var n,r,o,i;if(e.transform&&t.css){if(!(i=e.transform(t.css)))return function(){};t.css=i}if(e.singleton){var l=u++;n=s||(s=v(e)),r=w.bind(null,n,l,!1),o=w.bind(null,n,l,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(t){var e=document.createElement("link");return t.attrs.type="text/css",t.attrs.rel="stylesheet",b(e,t.attrs),p(t,e),e}(e),r=function(t,e,n){var r=n.css,o=n.sourceMap,i=void 0===e.convertToAbsoluteUrls&&o;(e.convertToAbsoluteUrls||i)&&(r=f(r)),o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var l=new Blob([r],{type:"text/css"}),a=t.href;t.href=URL.createObjectURL(l),a&&URL.revokeObjectURL(a)}.bind(null,n,e),o=function(){y(n),n.href&&URL.revokeObjectURL(n.href)}):(n=v(e),r=function(t,e){var n=e.css,r=e.media;if(r&&t.setAttribute("media",r),t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){y(n)});return r(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;r(t=e)}else o()}}t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(e=e||{}).attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=l()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var n=h(t,e);return d(n,e),function(t){for(var r=[],o=0;o li::before {\n pointer-events: none;\n}\n.ql-clipboard {\n left: -100000px;\n height: 1px;\n overflow-y: hidden;\n position: absolute;\n top: 50%;\n}\n.ql-clipboard p {\n margin: 0;\n padding: 0;\n}\n.ql-editor {\n box-sizing: border-box;\n line-height: 1.42;\n height: 100%;\n outline: none;\n overflow-y: auto;\n padding: 12px 15px;\n tab-size: 4;\n -moz-tab-size: 4;\n text-align: left;\n white-space: pre-wrap;\n word-wrap: break-word;\n}\n.ql-editor > * {\n cursor: text;\n}\n.ql-editor p,\n.ql-editor ol,\n.ql-editor ul,\n.ql-editor pre,\n.ql-editor blockquote,\n.ql-editor h1,\n.ql-editor h2,\n.ql-editor h3,\n.ql-editor h4,\n.ql-editor h5,\n.ql-editor h6 {\n margin: 0;\n padding: 0;\n counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol,\n.ql-editor ul {\n padding-left: 1.5em;\n}\n.ql-editor ol > li,\n.ql-editor ul > li {\n list-style-type: none;\n}\n.ql-editor ul > li::before {\n content: '\\2022';\n}\n.ql-editor ul[data-checked=true],\n.ql-editor ul[data-checked=false] {\n pointer-events: none;\n}\n.ql-editor ul[data-checked=true] > li *,\n.ql-editor ul[data-checked=false] > li * {\n pointer-events: all;\n}\n.ql-editor ul[data-checked=true] > li::before,\n.ql-editor ul[data-checked=false] > li::before {\n color: #777;\n cursor: pointer;\n pointer-events: all;\n}\n.ql-editor ul[data-checked=true] > li::before {\n content: '\\2611';\n}\n.ql-editor ul[data-checked=false] > li::before {\n content: '\\2610';\n}\n.ql-editor li::before {\n display: inline-block;\n white-space: nowrap;\n width: 1.2em;\n}\n.ql-editor li:not(.ql-direction-rtl)::before {\n margin-left: -1.5em;\n margin-right: 0.3em;\n text-align: right;\n}\n.ql-editor li.ql-direction-rtl::before {\n margin-left: 0.3em;\n margin-right: -1.5em;\n}\n.ql-editor ol li:not(.ql-direction-rtl),\n.ql-editor ul li:not(.ql-direction-rtl) {\n padding-left: 1.5em;\n}\n.ql-editor ol li.ql-direction-rtl,\n.ql-editor ul li.ql-direction-rtl {\n padding-right: 1.5em;\n}\n.ql-editor ol li {\n counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n counter-increment: list-0;\n}\n.ql-editor ol li:before {\n content: counter(list-0, decimal) '. ';\n}\n.ql-editor ol li.ql-indent-1 {\n counter-increment: list-1;\n}\n.ql-editor ol li.ql-indent-1:before {\n content: counter(list-1, lower-alpha) '. ';\n}\n.ql-editor ol li.ql-indent-1 {\n counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-2 {\n counter-increment: list-2;\n}\n.ql-editor ol li.ql-indent-2:before {\n content: counter(list-2, lower-roman) '. ';\n}\n.ql-editor ol li.ql-indent-2 {\n counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-3 {\n counter-increment: list-3;\n}\n.ql-editor ol li.ql-indent-3:before {\n content: counter(list-3, decimal) '. ';\n}\n.ql-editor ol li.ql-indent-3 {\n counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-4 {\n counter-increment: list-4;\n}\n.ql-editor ol li.ql-indent-4:before {\n content: counter(list-4, lower-alpha) '. ';\n}\n.ql-editor ol li.ql-indent-4 {\n counter-reset: list-5 list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-5 {\n counter-increment: list-5;\n}\n.ql-editor ol li.ql-indent-5:before {\n content: counter(list-5, lower-roman) '. ';\n}\n.ql-editor ol li.ql-indent-5 {\n counter-reset: list-6 list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-6 {\n counter-increment: list-6;\n}\n.ql-editor ol li.ql-indent-6:before {\n content: counter(list-6, decimal) '. ';\n}\n.ql-editor ol li.ql-indent-6 {\n counter-reset: list-7 list-8 list-9;\n}\n.ql-editor ol li.ql-indent-7 {\n counter-increment: list-7;\n}\n.ql-editor ol li.ql-indent-7:before {\n content: counter(list-7, lower-alpha) '. ';\n}\n.ql-editor ol li.ql-indent-7 {\n counter-reset: list-8 list-9;\n}\n.ql-editor ol li.ql-indent-8 {\n counter-increment: list-8;\n}\n.ql-editor ol li.ql-indent-8:before {\n content: counter(list-8, lower-roman) '. ';\n}\n.ql-editor ol li.ql-indent-8 {\n counter-reset: list-9;\n}\n.ql-editor ol li.ql-indent-9 {\n counter-increment: list-9;\n}\n.ql-editor ol li.ql-indent-9:before {\n content: counter(list-9, decimal) '. ';\n}\n.ql-editor .ql-indent-1:not(.ql-direction-rtl) {\n padding-left: 3em;\n}\n.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {\n padding-left: 4.5em;\n}\n.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {\n padding-right: 3em;\n}\n.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {\n padding-right: 4.5em;\n}\n.ql-editor .ql-indent-2:not(.ql-direction-rtl) {\n padding-left: 6em;\n}\n.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {\n padding-left: 7.5em;\n}\n.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {\n padding-right: 6em;\n}\n.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {\n padding-right: 7.5em;\n}\n.ql-editor .ql-indent-3:not(.ql-direction-rtl) {\n padding-left: 9em;\n}\n.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {\n padding-left: 10.5em;\n}\n.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {\n padding-right: 9em;\n}\n.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {\n padding-right: 10.5em;\n}\n.ql-editor .ql-indent-4:not(.ql-direction-rtl) {\n padding-left: 12em;\n}\n.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {\n padding-left: 13.5em;\n}\n.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {\n padding-right: 12em;\n}\n.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {\n padding-right: 13.5em;\n}\n.ql-editor .ql-indent-5:not(.ql-direction-rtl) {\n padding-left: 15em;\n}\n.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {\n padding-left: 16.5em;\n}\n.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {\n padding-right: 15em;\n}\n.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {\n padding-right: 16.5em;\n}\n.ql-editor .ql-indent-6:not(.ql-direction-rtl) {\n padding-left: 18em;\n}\n.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {\n padding-left: 19.5em;\n}\n.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {\n padding-right: 18em;\n}\n.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {\n padding-right: 19.5em;\n}\n.ql-editor .ql-indent-7:not(.ql-direction-rtl) {\n padding-left: 21em;\n}\n.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {\n padding-left: 22.5em;\n}\n.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {\n padding-right: 21em;\n}\n.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {\n padding-right: 22.5em;\n}\n.ql-editor .ql-indent-8:not(.ql-direction-rtl) {\n padding-left: 24em;\n}\n.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {\n padding-left: 25.5em;\n}\n.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {\n padding-right: 24em;\n}\n.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {\n padding-right: 25.5em;\n}\n.ql-editor .ql-indent-9:not(.ql-direction-rtl) {\n padding-left: 27em;\n}\n.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {\n padding-left: 28.5em;\n}\n.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {\n padding-right: 27em;\n}\n.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {\n padding-right: 28.5em;\n}\n.ql-editor .ql-video {\n display: block;\n max-width: 100%;\n}\n.ql-editor .ql-video.ql-align-center {\n margin: 0 auto;\n}\n.ql-editor .ql-video.ql-align-right {\n margin: 0 0 0 auto;\n}\n.ql-editor .ql-bg-black {\n background-color: #000;\n}\n.ql-editor .ql-bg-red {\n background-color: #e60000;\n}\n.ql-editor .ql-bg-orange {\n background-color: #f90;\n}\n.ql-editor .ql-bg-yellow {\n background-color: #ff0;\n}\n.ql-editor .ql-bg-green {\n background-color: #008a00;\n}\n.ql-editor .ql-bg-blue {\n background-color: #06c;\n}\n.ql-editor .ql-bg-purple {\n background-color: #93f;\n}\n.ql-editor .ql-color-white {\n color: #fff;\n}\n.ql-editor .ql-color-red {\n color: #e60000;\n}\n.ql-editor .ql-color-orange {\n color: #f90;\n}\n.ql-editor .ql-color-yellow {\n color: #ff0;\n}\n.ql-editor .ql-color-green {\n color: #008a00;\n}\n.ql-editor .ql-color-blue {\n color: #06c;\n}\n.ql-editor .ql-color-purple {\n color: #93f;\n}\n.ql-editor .ql-font-serif {\n font-family: Georgia, Times New Roman, serif;\n}\n.ql-editor .ql-font-monospace {\n font-family: Monaco, Courier New, monospace;\n}\n.ql-editor .ql-size-small {\n font-size: 0.75em;\n}\n.ql-editor .ql-size-large {\n font-size: 1.5em;\n}\n.ql-editor .ql-size-huge {\n font-size: 2.5em;\n}\n.ql-editor .ql-direction-rtl {\n direction: rtl;\n text-align: inherit;\n}\n.ql-editor .ql-align-center {\n text-align: center;\n}\n.ql-editor .ql-align-justify {\n text-align: justify;\n}\n.ql-editor .ql-align-right {\n text-align: right;\n}\n.ql-editor.ql-blank::before {\n color: rgba(0,0,0,0.6);\n content: attr(data-placeholder);\n font-style: italic;\n left: 15px;\n pointer-events: none;\n position: absolute;\n right: 15px;\n}\n",""])},97:function(t,e,n){var r=n(96);"string"==typeof r&&(r=[[t.i,r,""]]);n(5)(r,{hmr:!0,transform:void 0,insertInto:void 0}),r.locals&&(t.exports=r.locals)},98:function(t,e){var n={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==n.call(t)}},99:function(t,e){e.read=function(t,e,n,r,o){var i,l,a=8*o-r-1,s=(1<>1,c=-7,f=n?o-1:0,d=n?-1:1,h=t[e+f];for(f+=d,i=h&(1<<-c)-1,h>>=-c,c+=a;c>0;i=256*i+t[e+f],f+=d,c-=8);for(l=i&(1<<-c)-1,i>>=-c,c+=r;c>0;l=256*l+t[e+f],f+=d,c-=8);if(0===i)i=1-u;else{if(i===s)return l?NaN:1/0*(h?-1:1);l+=Math.pow(2,r),i-=u}return(h?-1:1)*l*Math.pow(2,i-r)},e.write=function(t,e,n,r,o,i){var l,a,s,u=8*i-o-1,c=(1<>1,d=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,h=r?0:i-1,p=r?1:-1,y=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(a=isNaN(e)?1:0,l=c):(l=Math.floor(Math.log(e)/Math.LN2),e*(s=Math.pow(2,-l))<1&&(l--,s*=2),(e+=l+f>=1?d/s:d*Math.pow(2,1-f))*s>=2&&(l++,s/=2),l+f>=c?(a=0,l=c):l+f>=1?(a=(e*s-1)*Math.pow(2,o),l+=f):(a=e*Math.pow(2,f-1)*Math.pow(2,o),l=0));o>=8;t[n+h]=255&a,h+=p,a/=256,o-=8);for(l=l<0;t[n+h]=255&l,h+=p,l/=256,u-=8);t[n+h-p]|=128*y}}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/wysiwyg-full/Readonly.js b/public/extensions/core/interfaces/wysiwyg-full/Readonly.js new file mode 100644 index 0000000000..8f79871db3 --- /dev/null +++ b/public/extensions/core/interfaces/wysiwyg-full/Readonly.js @@ -0,0 +1 @@ +var __DirectusExtension__=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=83)}({0:function(e,t,n){"use strict";function r(e,t,n,r,o,i,u,l){var s=typeof(e=e||{}).default;"object"!==s&&"function"!==s||(e=e.default);var a,c="function"==typeof e?e.options:e;if(t&&(c.render=t,c.staticRenderFns=n,c._compiled=!0),r&&(c.functional=!0),i&&(c._scopeId=i),u?(a=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(u)},c._ssrRegister=a):o&&(a=l?function(){o.call(this,this.$root.$options.shadowRoot)}:o),a)if(c.functional){c._injectStyles=a;var f=c.render;c.render=function(e,t){return a.call(t),f(e,t)}}else{var d=c.beforeCreate;c.beforeCreate=d?[].concat(d,a):[a]}return{exports:e,options:c}}n.d(t,"a",function(){return r})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},83:function(e,t,n){"use strict";n.r(t);var r=n(1),o={name:"readonly-wysiwyg",mixins:[n.n(r).a]},i=n(0),u=Object(i.a)(o,function(){var e=this.$createElement;return(this._self._c||e)("div",[this._v(this._s(this.value))])},[],!1,null,null,null);t.default=u.exports}}); \ No newline at end of file diff --git a/public/extensions/core/interfaces/wysiwyg-full/meta.json b/public/extensions/core/interfaces/wysiwyg-full/meta.json new file mode 100644 index 0000000000..e3039a5b83 --- /dev/null +++ b/public/extensions/core/interfaces/wysiwyg-full/meta.json @@ -0,0 +1,107 @@ +{ + "name": "$t:wysiwyg_full", + "version": "1.0.0", + "datatypes": { + "TEXT": null, + "TINYTEXT": null, + "MEDIUMTEXT": null, + "LONGTEXT": null, + "VARCHAR": 255 + }, + "options": { + "placeholder": { + "name": "$t:placeholder", + "comment": "$t:placeholder_comment", + "interface": "text-input", + "length": 200 + }, + "inline": { + "name": "$t:inline", + "comment": "$t:inline_comment", + "interface": "checkboxes", + "default": ["bold", "italic", "underline", "code", "link", "script"], + "options": { + "wrap": false, + "choices": { + "background": "$t:bg_color", + "bold": "$t:bold", + "color": "$t:color", + "font": "$t:font", + "code": "$t:code", + "italic": "$t:italic", + "link": "$t:link", + "size": "$t:size", + "strike": "$t:strike", + "script": "$t:script", + "underline": "$t:underline" + } + } + }, + "block": { + "name": "$t:block", + "comment": "$t:block_comment", + "interface": "checkboxes", + "default": ["header", "blockquote", "list", "code-block"], + "options": { + "wrap": false, + "choices": { + "blockquote": "$t:blockquote", + "header": "$t:header", + "indent": "$t:indent", + "list": "$t:list", + "align": "$t:align", + "direction": "$t:direction", + "code-block": "$t:code-block" + } + } + }, + "embeds": { + "name": "$t:embeds", + "comment": "$t:embeds_comment", + "interface": "checkboxes", + "default": ["image", "video"], + "options": { + "wrap": false, + "choices": { + "formula": "$t:formula", + "image": "$t:image", + "video": "$t:video" + } + } + } + }, + "translation": { + "en-US": { + "wysiwyg_full": "Advanced WYSIWYG", + "placeholder": "Placeholder", + "placeholder_comment": "Enter placeholder text", + "inline": "Inline", + "inline_comment": "Select what inline operations to allow", + "block": "Block", + "block_comment": "Select what block operations to allow", + "embeds": "Embeds", + "embeds_comment": "Select what sources of embeds to allow", + "bg_color": "Background color", + "bold": "Bold", + "color": "Color", + "font": "Font family", + "code": "Inline code", + "italic": "Italic", + "link": "Link", + "size": "Size", + "strike": "Strikethrough", + "script": "Sub-/superscript", + "underline": "Underline", + "blockquote": "Blockquote", + "header": "Header", + "indent": "Indent", + "list": "List", + "align": "Align", + "direction": "Direction", + "code-block": "Code Block", + "formula": "Math formula", + "image": "Image", + "video": "Video" + } + } +} diff --git a/public/extensions/core/interfaces/wysiwyg/Interface.js b/public/extensions/core/interfaces/wysiwyg/Interface.js new file mode 100644 index 0000000000..40d147d300 --- /dev/null +++ b/public/extensions/core/interfaces/wysiwyg/Interface.js @@ -0,0 +1,5 @@ +var __DirectusExtension__=function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:i})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=85)}({0:function(e,t,n){"use strict";function i(e,t,n,i,o,r,s,a){var l=typeof(e=e||{}).default;"object"!==l&&"function"!==l||(e=e.default);var c,d="function"==typeof e?e.options:e;if(t&&(d.render=t,d.staticRenderFns=n,d._compiled=!0),i&&(d.functional=!0),r&&(d._scopeId=r),s?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(s)},d._ssrRegister=c):o&&(c=a?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(d.functional){d._injectStyles=c;var u=d.render;d.render=function(e,t){return c.call(t),u(e,t)}}else{var h=d.beforeCreate;d.beforeCreate=h?[].concat(h,c):[c]}return{exports:e,options:d}}n.d(t,"a",function(){return i})},1:function(e,t){e.exports={props:{name:{type:String,required:!0},value:{type:null,default:null},type:{type:String,required:!0},length:{type:[String,Number],default:null},readonly:{type:Boolean,default:!1},required:{type:Boolean,default:!1},options:{type:Object,default:function(){return{}}},newItem:{type:Boolean,default:!1},relationship:{type:Object,default:null}}}},11:function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},2:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){for(var n=[],i={},o=0;o1?t:this.data.length),n,this.encoding)},r.toString=function(){return"[object Blob]"},r.close=function(){this.size=0,delete this.data},n}(e);e.Blob=function(e,n){var i=n&&n.type||"",o=new t;if(e)for(var r=0,s=e.length;r-1,isMac:t.navigator.platform.toUpperCase().indexOf("MAC")>=0,keyCode:{BACKSPACE:8,TAB:9,ENTER:13,ESCAPE:27,SPACE:32,DELETE:46,K:75,M:77,V:86},isMetaCtrlKey:function(e){return!!(s.isMac&&e.metaKey||!s.isMac&&e.ctrlKey)},isKey:function(e,t){var n=s.getKeyCode(e);return!1===Array.isArray(t)?n===t:-1!==t.indexOf(n)},getKeyCode:function(e){var t=e.which;return null===t&&(t=null!==e.charCode?e.charCode:e.keyCode),t},blockContainerElementNames:["p","h1","h2","h3","h4","h5","h6","blockquote","pre","ul","li","ol","address","article","aside","audio","canvas","dd","dl","dt","fieldset","figcaption","figure","footer","form","header","hgroup","main","nav","noscript","output","section","video","table","thead","tbody","tfoot","tr","th","td"],emptyElementNames:["br","col","colgroup","hr","img","input","source","wbr"],extend:function(){var e=[!0].concat(Array.prototype.slice.call(arguments));return n.apply(this,e)},defaults:function(){var e=[!1].concat(Array.prototype.slice.call(arguments));return n.apply(this,e)},createLink:function(e,t,n,i){var o=e.createElement("a");return s.moveTextRangeIntoElement(t[0],t[t.length-1],o),o.setAttribute("href",n),i&&("_blank"===i&&o.setAttribute("rel","noopener noreferrer"),o.setAttribute("target",i)),o},findOrCreateMatchingTextNodes:function(e,t,n){for(var i=e.createTreeWalker(t,NodeFilter.SHOW_ALL,null,!1),o=[],r=0,a=!1,l=null,c=null;null!==(l=i.nextNode());)if(!(l.nodeType>3))if(3===l.nodeType){if(!a&&n.startn.end+1)throw new Error("PerformLinking overshot the target!");a&&o.push(c||l),r+=l.nodeValue.length,null!==c&&(r+=c.nodeValue.length,i.nextNode()),c=null}else"img"===l.tagName.toLowerCase()&&(!a&&n.start<=r&&(a=!0),a&&o.push(l));return o},splitStartNodeIfNeeded:function(e,t,n){return t!==n?e.splitText(t-n):null},splitEndNodeIfNeeded:function(e,t,n,i){var o,r;o=i+e.nodeValue.length+(t?t.nodeValue.length:0)-1,r=n-i-(t?e.nodeValue.length:0),o>=n&&i!==o&&0!==r&&(t||e).splitText(r)},splitByBlockElements:function(t){if(3!==t.nodeType&&1!==t.nodeType)return[];var n=[],i=e.util.blockContainerElementNames.join(",");if(3===t.nodeType||0===t.querySelectorAll(i).length)return[t];for(var o=0;o0)break;i=r.nextNode()}return i},findPreviousSibling:function(e){if(!e||s.isMediumEditorElement(e))return!1;for(var t=e.previousSibling;!t&&!s.isMediumEditorElement(e.parentNode);)t=(e=e.parentNode).previousSibling;return t},isDescendant:function(e,t,n){if(!e||!t)return!1;if(e===t)return!!n;if(1!==e.nodeType)return!1;if(i||3!==t.nodeType)return e.contains(t);for(var o=t.parentNode;null!==o;){if(o===e)return!0;o=o.parentNode}return!1},isElement:function(e){return!(!e||1!==e.nodeType)},throttle:function(e,t){var n,i,o,r=null,s=0,a=function(){s=Date.now(),r=null,o=e.apply(n,i),r||(n=i=null)};return t||0===t||(t=50),function(){var l=Date.now(),c=t-(l-s);return n=this,i=arguments,c<=0||c>t?(r&&(clearTimeout(r),r=null),s=l,o=e.apply(n,i),r||(n=i=null)):r||(r=setTimeout(a,c)),o}},traverseUp:function(e,t){if(!e)return!1;do{if(1===e.nodeType){if(t(e))return e;if(s.isMediumEditorElement(e))return!1}e=e.parentNode}while(e);return!1},htmlEntities:function(e){return String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")},insertHTMLCommand:function(t,n){var i,o,r,a,l,c,d,u=!1,h=["insertHTML",!1,n];if(!e.util.isEdge&&t.queryCommandSupported("insertHTML"))try{return t.execCommand.apply(t,h)}catch(e){}if((i=t.getSelection()).rangeCount){if(d=(o=i.getRangeAt(0)).commonAncestorContainer,s.isMediumEditorElement(d)&&!d.firstChild)o.selectNode(d.appendChild(t.createTextNode("")));else if(3===d.nodeType&&0===o.startOffset&&o.endOffset===d.nodeValue.length||3!==d.nodeType&&d.innerHTML===o.toString()){for(;!s.isMediumEditorElement(d)&&d.parentNode&&1===d.parentNode.childNodes.length&&!s.isMediumEditorElement(d.parentNode);)d=d.parentNode;o.selectNode(d)}for(o.deleteContents(),(r=t.createElement("div")).innerHTML=n,a=t.createDocumentFragment();r.firstChild;)l=r.firstChild,c=a.appendChild(l);o.insertNode(a),c&&((o=o.cloneRange()).setStartAfter(c),o.collapse(!0),e.selection.selectRange(t,o)),u=!0}return t.execCommand.callListeners&&t.execCommand.callListeners(h,u),u},execFormatBlock:function(t,n){var i=s.getTopBlockContainer(e.selection.getSelectionStart(t));if("blockquote"===n){if(i&&Array.prototype.slice.call(i.childNodes).some(function(e){return s.isBlockContainer(e)}))return t.execCommand("outdent",!1,null);if(s.isIE)return t.execCommand("indent",!1,n)}if(i&&n===i.nodeName.toLowerCase()&&(n="p"),s.isIE&&(n="<"+n+">"),i&&"blockquote"===i.nodeName.toLowerCase()){if(s.isIE&&"

    "===n)return t.execCommand("outdent",!1,n);if((s.isFF||s.isEdge)&&"p"===n)return Array.prototype.slice.call(i.childNodes).some(function(e){return!s.isBlockContainer(e)})&&t.execCommand("formatBlock",!1,n),t.execCommand("outdent",!1,n)}return t.execCommand("formatBlock",!1,n)},setTargetBlank:function(e,t){var n,i=t||!1;if("a"===e.nodeName.toLowerCase())e.target="_blank",e.rel="noopener noreferrer";else for(e=e.getElementsByTagName("a"),n=0;ni?(o=o.parentNode,n-=1):(r=r.parentNode,i-=1);for(;o!==r;)o=o.parentNode,r=r.parentNode;return o},isElementAtBeginningOfBlock:function(e){for(var t;!s.isBlockContainer(e)&&!s.isMediumEditorElement(e);){for(t=e;t=t.previousSibling;)if((3===t.nodeType?t.nodeValue:t.textContent).length>0)return!1;e=e.parentNode}return!0},isMediumEditorElement:function(e){return e&&e.getAttribute&&!!e.getAttribute("data-medium-editor-element")},getContainerEditorElement:function(e){return s.traverseUp(e,function(e){return s.isMediumEditorElement(e)})},isBlockContainer:function(e){return e&&3!==e.nodeType&&-1!==s.blockContainerElementNames.indexOf(e.nodeName.toLowerCase())},getClosestBlockContainer:function(e){return s.traverseUp(e,function(e){return s.isBlockContainer(e)||s.isMediumEditorElement(e)})},getTopBlockContainer:function(e){var t=!!s.isBlockContainer(e)&&e;return s.traverseUp(e,function(e){return s.isBlockContainer(e)&&(t=e),!(t||!s.isMediumEditorElement(e)||(t=e,0))}),t},getFirstSelectableLeafNode:function(e){for(;e&&e.firstChild;)e=e.firstChild;if("table"===(e=s.traverseUp(e,function(e){return-1===s.emptyElementNames.indexOf(e.nodeName.toLowerCase())})).nodeName.toLowerCase()){var t=e.querySelector("th, td");t&&(e=t)}return e},getFirstTextNode:function(e){return s.warn("getFirstTextNode is deprecated and will be removed in version 6.0.0"),s._getFirstTextNode(e)},_getFirstTextNode:function(e){if(3===e.nodeType)return e;for(var t=0;t0){var o,r=i.getRangeAt(0),s=r.cloneRange();s.selectNodeContents(e),s.setEnd(r.startContainer,r.startOffset),n={start:o=s.toString().length,end:o+r.toString().length},this.doesRangeStartWithImages(r,t)&&(n.startsWithImage=!0);var a=this.getTrailingImageCount(e,n,r.endContainer,r.endOffset);if(a&&(n.trailingImageCount=a),0!==o){var l=this.getIndexRelativeToAdjacentEmptyBlocks(t,e,r.startContainer,r.startOffset);-1!==l&&(n.emptyBlocksIndex=l)}}return n},importSelection:function(e,t,n,i){if(e&&t){var o=n.createRange();o.setStart(t,0),o.collapse(!0);var r,s=t,a=[],l=0,c=!1,d=!1,u=0,h=!1,m=!1,f=null;for((i||e.startsWithImage||void 0!==e.emptyBlocksIndex)&&(m=!0);!h&&s;)if(s.nodeType>3)s=a.pop();else{if(3!==s.nodeType||d){if(e.trailingImageCount&&d&&("img"===s.nodeName.toLowerCase()&&u++,u===e.trailingImageCount)){for(var p=0;s.parentNode.childNodes[p]!==s;)p++;o.setEnd(s.parentNode,p+1),h=!0}if(!h&&1===s.nodeType)for(var g=s.childNodes.length-1;g>=0;)a.push(s.childNodes[g]),g-=1}else r=l+s.length,!c&&e.start>=l&&e.start<=r&&(m||e.start=l&&e.end<=r&&(e.trailingImageCount?d=!0:(o.setEnd(s,e.end-l),h=!0)),l=r;h||(s=a.pop())}!c&&f&&(o.setStart(f,f.length),o.setEnd(f,f.length)),void 0!==e.emptyBlocksIndex&&(o=this.importSelectionMoveCursorPastBlocks(n,t,e.emptyBlocksIndex,o)),i&&(o=this.importSelectionMoveCursorPastAnchor(e,o)),this.selectRange(n,o)}},importSelectionMoveCursorPastAnchor:function(t,n){if(t.start===t.end&&3===n.startContainer.nodeType&&n.startOffset===n.startContainer.nodeValue.length&&e.util.traverseUp(n.startContainer,function(e){return"a"===e.nodeName.toLowerCase()})){for(var i=n.startContainer,o=n.startContainer.parentNode;null!==o&&"a"!==o.nodeName.toLowerCase();)o.childNodes[o.childNodes.length-1]!==i?o=null:(i=o,o=o.parentNode);if(null!==o&&"a"===o.nodeName.toLowerCase()){for(var r=null,s=0;null===r&&s0)break}else s===l.currentNode&&(a=l.currentNode);return a||(a=s),r.setStart(e.util.getFirstSelectableLeafNode(a),0),r},getIndexRelativeToAdjacentEmptyBlocks:function(n,i,o,r){if(o.textContent.length>0&&r>0)return-1;var s=o;if(3!==s.nodeType&&(s=o.childNodes[r]),s){if(!e.util.isElementAtBeginningOfBlock(s))return-1;var a=e.util.findPreviousSibling(s);if(!a)return-1;if(a.nodeValue)return-1}for(var l=e.util.getClosestBlockContainer(o),c=n.createTreeWalker(i,NodeFilter.SHOW_ELEMENT,t,!1),d=0;c.nextNode();){var u=""===c.currentNode.textContent;if((u||d>0)&&(d+=1),c.currentNode===l)return d;u||(d=0)}return d},doesRangeStartWithImages:function(e,t){if(0!==e.startOffset||1!==e.startContainer.nodeType)return!1;if("img"===e.startContainer.nodeName.toLowerCase())return!0;var n=e.startContainer.querySelector("img");if(!n)return!1;for(var i=t.createTreeWalker(e.startContainer,NodeFilter.SHOW_ALL,null,!1);i.nextNode();){var o=i.currentNode;if(o===n)break;if(o.nodeValue)return!1}return!0},getTrailingImageCount:function(e,t,n,i){if(0===i||1!==n.nodeType)return 0;if("img"!==n.nodeName.toLowerCase()&&!n.querySelector("img"))return 0;for(var o=n.childNodes[i-1];o.hasChildNodes();)o=o.lastChild;for(var r,s=e,a=[],l=0,c=!1,d=!1,u=!1,h=0;!u&&s;)if(s.nodeType>3)s=a.pop();else{if(3!==s.nodeType||d){if("img"===s.nodeName.toLowerCase()&&h++,s===o)u=!0;else if(1===s.nodeType)for(var m=s.childNodes.length-1;m>=0;)a.push(s.childNodes[m]),m-=1}else h=0,r=l+s.length,!c&&t.start>=l&&t.start<=r&&(c=!0),c&&t.end>=l&&t.end<=r&&(d=!0),l=r;u||(s=a.pop())}return h},selectionContainsContent:function(e){var t=e.getSelection();if(!t||t.isCollapsed||!t.rangeCount)return!1;if(""!==t.toString().trim())return!0;var n=this.getSelectedParentElement(t.getRangeAt(0));return!(!n||!("img"===n.nodeName.toLowerCase()||1===n.nodeType&&n.querySelector("img")))},selectionInContentEditableFalse:function(e){var t,n=this.findMatchingSelectionParent(function(e){var n=e&&e.getAttribute("contenteditable");return"true"===n&&(t=!0),"#text"!==e.nodeName&&"false"===n},e);return!t&&n},getSelectionHtml:function(e){var t,n,i,o="",r=e.getSelection();if(r.rangeCount){for(i=e.createElement("div"),t=0,n=r.rangeCount;t-1?[t]:t,Array.prototype.forEach.call(t,function(e){e.addEventListener(n,i,o),this.events.push([e,n,i,o])}.bind(this))},detachDOMEvent:function(t,n,i,o){var r,s,a=this.base.options.contentWindow,l=this.base.options.ownerDocument;t&&(t=e.util.isElement(t)||[a,l].indexOf(t)>-1?[t]:t,Array.prototype.forEach.call(t,function(e){-1!==(r=this.indexOfListener(e,n,i,o))&&(s=this.events.splice(r,1)[0])[0].removeEventListener(s[1],s[2],s[3])}.bind(this)))},indexOfListener:function(e,t,n,i){var o,r,s;for(o=0,r=this.events.length;o0&&(i=-1!==o.indexOf(e.nodeName.toLowerCase())),!i&&this.style&&(t=this.style.value.split("|"),n=this.window.getComputedStyle(e,null).getPropertyValue(this.style.prop),t.forEach(function(e){this.knownState||((i=-1!==n.indexOf(e))||"text-decoration"!==this.style.prop)&&(this.knownState=i)},this)),i)}})).isBuiltInButton=function(t){return"string"==typeof t&&e.extensions.button.prototype.defaults.hasOwnProperty(t)},e.extensions.button=n,e.extensions.button.prototype.defaults={bold:{name:"bold",action:"bold",aria:"bold",tagNames:["b","strong"],style:{prop:"font-weight",value:"700|bold"},useQueryState:!0,contentDefault:"B",contentFA:''},italic:{name:"italic",action:"italic",aria:"italic",tagNames:["i","em"],style:{prop:"font-style",value:"italic"},useQueryState:!0,contentDefault:"I",contentFA:''},underline:{name:"underline",action:"underline",aria:"underline",tagNames:["u"],style:{prop:"text-decoration",value:"underline"},useQueryState:!0,contentDefault:"U",contentFA:''},strikethrough:{name:"strikethrough",action:"strikethrough",aria:"strike through",tagNames:["strike"],style:{prop:"text-decoration",value:"line-through"},useQueryState:!0,contentDefault:"A",contentFA:''},superscript:{name:"superscript",action:"superscript",aria:"superscript",tagNames:["sup"],contentDefault:"x1",contentFA:''},subscript:{name:"subscript",action:"subscript",aria:"subscript",tagNames:["sub"],contentDefault:"x1",contentFA:''},image:{name:"image",action:"image",aria:"image",tagNames:["img"],contentDefault:"image",contentFA:''},html:{name:"html",action:"html",aria:"evaluate html",tagNames:["iframe","object"],contentDefault:"html",contentFA:''},orderedlist:{name:"orderedlist",action:"insertorderedlist",aria:"ordered list",tagNames:["ol"],useQueryState:!0,contentDefault:"1.",contentFA:''},unorderedlist:{name:"unorderedlist",action:"insertunorderedlist",aria:"unordered list",tagNames:["ul"],useQueryState:!0,contentDefault:"",contentFA:''},indent:{name:"indent",action:"indent",aria:"indent",tagNames:[],contentDefault:"",contentFA:''},outdent:{name:"outdent",action:"outdent",aria:"outdent",tagNames:[],contentDefault:"",contentFA:''},justifyCenter:{name:"justifyCenter",action:"justifyCenter",aria:"center justify",tagNames:[],style:{prop:"text-align",value:"center"},contentDefault:"C",contentFA:''},justifyFull:{name:"justifyFull",action:"justifyFull",aria:"full justify",tagNames:[],style:{prop:"text-align",value:"justify"},contentDefault:"J",contentFA:''},justifyLeft:{name:"justifyLeft",action:"justifyLeft",aria:"left justify",tagNames:[],style:{prop:"text-align",value:"left"},contentDefault:"L",contentFA:''},justifyRight:{name:"justifyRight",action:"justifyRight",aria:"right justify",tagNames:[],style:{prop:"text-align",value:"right"},contentDefault:"R",contentFA:''},removeFormat:{name:"removeFormat",aria:"remove formatting",action:"removeFormat",contentDefault:"X",contentFA:''},quote:{name:"quote",action:"append-blockquote",aria:"blockquote",tagNames:["blockquote"],contentDefault:"",contentFA:''},pre:{name:"pre",action:"append-pre",aria:"preformatted text",tagNames:["pre"],contentDefault:"0101",contentFA:''},h1:{name:"h1",action:"append-h1",aria:"header type one",tagNames:["h1"],contentDefault:"H1",contentFA:'1'},h2:{name:"h2",action:"append-h2",aria:"header type two",tagNames:["h2"],contentDefault:"H2",contentFA:'2'},h3:{name:"h3",action:"append-h3",aria:"header type three",tagNames:["h3"],contentDefault:"H3",contentFA:'3'},h4:{name:"h4",action:"append-h4",aria:"header type four",tagNames:["h4"],contentDefault:"H4",contentFA:'4'},h5:{name:"h5",action:"append-h5",aria:"header type five",tagNames:["h5"],contentDefault:"H5",contentFA:'5'},h6:{name:"h6",action:"append-h6",aria:"header type six",tagNames:["h6"],contentDefault:"H6",contentFA:'6'}},i=e.extensions.button.extend({init:function(){e.extensions.button.prototype.init.apply(this,arguments)},formSaveLabel:"✓",formCloseLabel:"×",activeClass:"medium-editor-toolbar-form-active",hasForm:!0,getForm:function(){},isDisplayed:function(){return!!this.hasForm&&this.getForm().classList.contains(this.activeClass)},showForm:function(){this.hasForm&&this.getForm().classList.add(this.activeClass)},hideForm:function(){this.hasForm&&this.getForm().classList.remove(this.activeClass)},showToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.showToolbarDefaultActions()},hideToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.hideToolbarDefaultActions()},setToolbarPosition:function(){var e=this.base.getExtensionByName("toolbar");e&&e.setToolbarPosition()}}),e.extensions.form=i,o=e.extensions.form.extend({customClassOption:null,customClassOptionText:"Button",linkValidation:!1,placeholderText:"Paste or type a link",targetCheckbox:!1,targetCheckboxText:"Open in new window",name:"anchor",action:"createLink",aria:"link",tagNames:["a"],contentDefault:"#",contentFA:'',init:function(){e.extensions.form.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this))},handleClick:function(t){t.preventDefault(),t.stopPropagation();var n=e.selection.getSelectionRange(this.document);return"a"===n.startContainer.nodeName.toLowerCase()||"a"===n.endContainer.nodeName.toLowerCase()||e.util.getClosestTag(e.selection.getSelectedParentElement(n),"a")?this.execAction("unlink"):(this.isDisplayed()||this.showForm(),!1)},handleKeydown:function(t){e.util.isKey(t,e.util.keyCode.K)&&e.util.isMetaCtrlKey(t)&&!t.shiftKey&&this.handleClick(t)},getForm:function(){return this.form||(this.form=this.createForm()),this.form},getTemplate:function(){var e=[''];return e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formSaveLabel,""),e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formCloseLabel,""),this.targetCheckbox&&e.push('

    ','','","
    "),this.customClassOption&&e.push('
    ','',"","
    "),e.join("")},isDisplayed:function(){return e.extensions.form.prototype.isDisplayed.apply(this)},hideForm:function(){e.extensions.form.prototype.hideForm.apply(this),this.getInput().value=""},showForm:function(t){var n=this.getInput(),i=this.getAnchorTargetCheckbox(),o=this.getAnchorButtonCheckbox();if("string"==typeof(t=t||{value:""})&&(t={value:t}),this.base.saveSelection(),this.hideToolbarDefaultActions(),e.extensions.form.prototype.showForm.apply(this),this.setToolbarPosition(),n.value=t.value,n.focus(),i&&(i.checked="_blank"===t.target),o){var r=t.buttonClass?t.buttonClass.split(" "):[];o.checked=-1!==r.indexOf(this.customClassOption)}},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},getFormOpts:function(){var e=this.getAnchorTargetCheckbox(),t=this.getAnchorButtonCheckbox(),n={value:this.getInput().value.trim()};return this.linkValidation&&(n.value=this.checkLinkFormat(n.value)),n.target="_self",e&&e.checked&&(n.target="_blank"),t&&t.checked&&(n.buttonClass=this.customClassOption),n},doFormSave:function(){var e=this.getFormOpts();this.completeFormSave(e)},completeFormSave:function(e){this.base.restoreSelection(),this.execAction(this.action,e),this.base.checkSelection()},ensureEncodedUri:function(e){return e===decodeURI(e)?encodeURI(e):e},ensureEncodedUriComponent:function(e){return e===decodeURIComponent(e)?encodeURIComponent(e):e},ensureEncodedParam:function(e){var t=e.split("="),n=t[0],i=t[1];return n+(void 0===i?"":"="+this.ensureEncodedUriComponent(i))},ensureEncodedQuery:function(e){return e.split("&").map(this.ensureEncodedParam.bind(this)).join("&")},checkLinkFormat:function(e){var t=/^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i.test(e),n="",i=e.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),o=i[1],r=i[2],s=i[3];if(/^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/.test(e))return"tel:"+e;if(!t){var a=o.split("/")[0];(a.match(/.+(\.|:).+/)||"localhost"===a)&&(n="http://")}return n+this.ensureEncodedUri(o)+(void 0===r?"":"?"+this.ensureEncodedQuery(r))+(void 0===s?"":"#"+s)},doFormCancel:function(){this.base.restoreSelection(),this.base.checkSelection()},attachFormEvents:function(e){var t=e.querySelector(".medium-editor-toolbar-close"),n=e.querySelector(".medium-editor-toolbar-save"),i=e.querySelector(".medium-editor-toolbar-input");this.on(e,"click",this.handleFormClick.bind(this)),this.on(i,"keyup",this.handleTextboxKeyup.bind(this)),this.on(t,"click",this.handleCloseClick.bind(this)),this.on(n,"click",this.handleSaveClick.bind(this),!0)},createForm:function(){var e=this.document.createElement("div");return e.className="medium-editor-toolbar-form",e.id="medium-editor-toolbar-form-anchor-"+this.getEditorId(),e.innerHTML=this.getTemplate(),this.attachFormEvents(e),e},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},getAnchorTargetCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-target")},getAnchorButtonCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-button")},handleTextboxKeyup:function(t){if(t.keyCode===e.util.keyCode.ENTER)return t.preventDefault(),void this.doFormSave();t.keyCode===e.util.keyCode.ESCAPE&&(t.preventDefault(),this.doFormCancel())},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),e.extensions.anchor=o,r=e.Extension.extend({name:"anchor-preview",hideDelay:500,previewValueSelector:"a",showWhenToolbarIsVisible:!1,showOnEmptyLinks:!0,init:function(){this.anchorPreview=this.createPreview(),this.getEditorOption("elementsContainer").appendChild(this.anchorPreview),this.attachToEditables()},getInteractionElements:function(){return this.getPreviewElement()},getPreviewElement:function(){return this.anchorPreview},createPreview:function(){var e=this.document.createElement("div");return e.id="medium-editor-anchor-preview-"+this.getEditorId(),e.className="medium-editor-anchor-preview",e.innerHTML=this.getTemplate(),this.on(e,"click",this.handleClick.bind(this)),e},getTemplate:function(){return'
    '},destroy:function(){this.anchorPreview&&(this.anchorPreview.parentNode&&this.anchorPreview.parentNode.removeChild(this.anchorPreview),delete this.anchorPreview)},hidePreview:function(){this.anchorPreview&&this.anchorPreview.classList.remove("medium-editor-anchor-preview-active"),this.activeAnchor=null},showPreview:function(e){return!(!this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")&&!e.getAttribute("data-disable-preview"))||(this.previewValueSelector&&(this.anchorPreview.querySelector(this.previewValueSelector).textContent=e.attributes.href.value,this.anchorPreview.querySelector(this.previewValueSelector).href=e.attributes.href.value),this.anchorPreview.classList.add("medium-toolbar-arrow-over"),this.anchorPreview.classList.remove("medium-toolbar-arrow-under"),this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||this.anchorPreview.classList.add("medium-editor-anchor-preview-active"),this.activeAnchor=e,this.positionPreview(),this.attachPreviewHandlers(),this)},positionPreview:function(e){e=e||this.activeAnchor;var t,n,i,o,r,s=this.window.innerWidth,a=this.anchorPreview.offsetHeight,l=e.getBoundingClientRect(),c=this.diffLeft,d=this.diffTop,u=this.getEditorOption("elementsContainer"),h=["absolute","fixed"].indexOf(window.getComputedStyle(u).getPropertyValue("position"))>-1,m={};t=this.anchorPreview.offsetWidth/2;var f=this.base.getExtensionByName("toolbar");f&&(c=f.diffLeft,d=f.diffTop),n=c-t,h?(o=u.getBoundingClientRect(),["top","left"].forEach(function(e){m[e]=l[e]-o[e]}),m.width=l.width,m.height=l.height,l=m,s=o.width,r=u.scrollTop):r=this.window.pageYOffset,i=l.left+l.width/2,r+=a+l.top+l.height-d-this.anchorPreview.offsetHeight,this.anchorPreview.style.top=Math.round(r)+"px",this.anchorPreview.style.right="initial",ithis.hideDelay&&this.detachPreviewHandlers()},detachPreviewHandlers:function(){clearInterval(this.intervalTimer),this.instanceHandlePreviewMouseover&&(this.off(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.activeAnchor&&(this.off(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout))),this.hidePreview(),this.hovering=this.instanceHandlePreviewMouseover=this.instanceHandlePreviewMouseout=null},attachPreviewHandlers:function(){this.lastOver=(new Date).getTime(),this.hovering=!0,this.instanceHandlePreviewMouseover=this.handlePreviewMouseover.bind(this),this.instanceHandlePreviewMouseout=this.handlePreviewMouseout.bind(this),this.intervalTimer=setInterval(this.updatePreview.bind(this),200),this.on(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.on(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout)}}),e.extensions.anchorPreview=r,function(){var t,n,i,o,r;t=[" ","\t","\n","\r"," "," "," "," "," ","\u2028","\u2029"],i="(((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.]("+(n="com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw")+")\\/)\\S+(?:[^\\s`!\\[\\]{};:'\".,?«»“”‘’])))|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.("+n+"))",o=new RegExp("^("+n+")$","i"),r=new RegExp(i,"gi");var s=e.Extension.extend({init:function(){e.Extension.prototype.init.apply(this,arguments),this.disableEventHandling=!1,this.subscribe("editableKeypress",this.onKeypress.bind(this)),this.subscribe("editableBlur",this.onBlur.bind(this)),this.document.execCommand("AutoUrlDetect",!1,!1)},isLastInstance:function(){for(var e=0,t=0;t0&&null!==o;)(i=(n=r.currentNode).nodeValue).length>t?(o=n.splitText(i.length-t),t=0):(o=r.previousNode(),t-=i.length);return o},performLinkingWithinElement:function(t){for(var n=this.findLinkableText(t),i=0;i1;)o.appendChild(i.childNodes[1])}});e.extensions.autoLink=s}(),function(){var t="medium-editor-dragover";function n(n){var i=e.util.getContainerEditorElement(n);Array.prototype.slice.call(i.parentElement.querySelectorAll("."+t)).forEach(function(e){e.classList.remove(t)})}var i=e.Extension.extend({name:"fileDragging",allowedTypes:["image"],init:function(){e.Extension.prototype.init.apply(this,arguments),this.subscribe("editableDrag",this.handleDrag.bind(this)),this.subscribe("editableDrop",this.handleDrop.bind(this))},handleDrag:function(e){e.preventDefault(),e.dataTransfer.dropEffect="copy";var i=e.target.classList?e.target:e.target.parentElement;n(i),"dragover"===e.type&&i.classList.add(t)},handleDrop:function(e){e.preventDefault(),e.stopPropagation(),this.base.selectElement(e.target);var t=this.base.exportSelection();t.start=t.end,this.base.importSelection(t),e.dataTransfer.files&&Array.prototype.slice.call(e.dataTransfer.files).forEach(function(e){this.isAllowedFile(e)&&e.type.match("image")&&this.insertImageFile(e)},this),n(e.target)},isAllowedFile:function(e){return this.allowedTypes.some(function(t){return!!e.type.match(t)})},insertImageFile:function(t){if("function"==typeof FileReader){var n=new FileReader;n.readAsDataURL(t),n.addEventListener("load",function(t){var n=this.document.createElement("img");n.src=t.target.result,e.util.insertHTMLCommand(this.document,n.outerHTML)}.bind(this))}}});e.extensions.fileDragging=i}(),s=e.Extension.extend({name:"keyboard-commands",commands:[{command:"bold",key:"B",meta:!0,shift:!1,alt:!1},{command:"italic",key:"I",meta:!0,shift:!1,alt:!1},{command:"underline",key:"U",meta:!0,shift:!1,alt:!1}],init:function(){e.Extension.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.keys={},this.commands.forEach(function(e){var t=e.key.charCodeAt(0);this.keys[t]||(this.keys[t]=[]),this.keys[t].push(e)},this)},handleKeydown:function(t){var n=e.util.getKeyCode(t);if(this.keys[n]){var i=e.util.isMetaCtrlKey(t),o=!!t.shiftKey,r=!!t.altKey;this.keys[n].forEach(function(e){e.meta!==i||e.shift!==o||e.alt!==r&&void 0!==e.alt||(t.preventDefault(),t.stopPropagation(),"function"==typeof e.command?e.command.apply(this):!1!==e.command&&this.execAction(e.command))},this)}}}),e.extensions.keyboardCommands=s,a=e.extensions.form.extend({name:"fontname",action:"fontName",aria:"change font name",contentDefault:"±",contentFA:'',fonts:["","Arial","Verdana","Times New Roman"],init:function(){e.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontName")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getSelect().value=""},showForm:function(e){var t=this.getSelect();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontName(),this.base.checkSelection()},createForm:function(){var e,t=this.document,n=t.createElement("div"),i=t.createElement("select"),o=t.createElement("a"),r=t.createElement("a");n.className="medium-editor-toolbar-form",n.id="medium-editor-toolbar-form-fontname-"+this.getEditorId(),this.on(n,"click",this.handleFormClick.bind(this));for(var s=0;s
    ':"✓",n.appendChild(r),this.on(r,"click",this.handleSaveClick.bind(this),!0),o.setAttribute("href","#"),o.className="medium-editor-toobar-close",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",n.appendChild(o),this.on(o,"click",this.handleCloseClick.bind(this)),n},getSelect:function(){return this.getForm().querySelector("select.medium-editor-toolbar-select")},clearFontName:function(){e.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("face")&&e.removeAttribute("face")})},handleFontChange:function(){var e=this.getSelect().value;""===e?this.clearFontName():this.execAction("fontName",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),e.extensions.fontName=a,l=e.extensions.form.extend({name:"fontsize",action:"fontSize",aria:"increase/decrease font size",contentDefault:"±",contentFA:'',init:function(){e.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontSize")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getInput().value=""},showForm:function(e){var t=this.getInput();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontSize(),this.base.checkSelection()},createForm:function(){var e=this.document,t=e.createElement("div"),n=e.createElement("input"),i=e.createElement("a"),o=e.createElement("a");return t.className="medium-editor-toolbar-form",t.id="medium-editor-toolbar-form-fontsize-"+this.getEditorId(),this.on(t,"click",this.handleFormClick.bind(this)),n.setAttribute("type","range"),n.setAttribute("min","1"),n.setAttribute("max","7"),n.className="medium-editor-toolbar-input",t.appendChild(n),this.on(n,"change",this.handleSliderChange.bind(this)),o.setAttribute("href","#"),o.className="medium-editor-toobar-save",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"✓",t.appendChild(o),this.on(o,"click",this.handleSaveClick.bind(this),!0),i.setAttribute("href","#"),i.className="medium-editor-toobar-close",i.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",t.appendChild(i),this.on(i,"click",this.handleCloseClick.bind(this)),t},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},clearFontSize:function(){e.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("size")&&e.removeAttribute("size")})},handleSliderChange:function(){var e=this.getInput().value;"4"===e?this.clearFontSize():this.execAction("fontSize",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),e.extensions.fontSize=l,function(){var t=null,n=null,i=function(e){e.stopPropagation()};function o(e,t,n){var i=e.clipboardData||t.clipboardData||n.dataTransfer,o={};if(!i)return o;if(i.getData){var r=i.getData("Text");r&&r.length>0&&(o["text/plain"]=r)}if(i.types)for(var s=0;s1)for(r=0;r"+e.util.htmlEntities(o[r])+"

    ");else s=e.util.htmlEntities(o[0]);e.util.insertHTMLCommand(this.document,s)}},handlePasteBinPaste:function(e){if(e.defaultPrevented)this.removePasteBin();else{var t=o(e,this.window,this.document),i=t["text/html"],r=t["text/plain"],s=n;if(!this.cleanPastedHTML||i)return e.preventDefault(),this.removePasteBin(),this.doPaste(i,r,s),void this.trigger("editablePaste",{currentTarget:s,target:s},s);setTimeout(function(){this.cleanPastedHTML&&(i=this.getPasteBinHtml()),this.removePasteBin(),this.doPaste(i,r,s),this.trigger("editablePaste",{currentTarget:s,target:s},s)}.bind(this),0)}},handleKeydown:function(t,n){e.util.isKey(t,e.util.keyCode.V)&&e.util.isMetaCtrlKey(t)&&(t.stopImmediatePropagation(),this.removePasteBin(),this.createPasteBin(n))},createPasteBin:function(o){var r,s=e.selection.getSelectionRange(this.document),a=this.window.pageYOffset;n=o,s&&((r=s.getClientRects()).length?a+=r[0].top:void 0!==s.startContainer.getBoundingClientRect?a+=s.startContainer.getBoundingClientRect().top:a+=s.getBoundingClientRect().top),t=s;var l=this.document.createElement("div");l.id=this.pasteBinId="medium-editor-pastebin-"+ +Date.now(),l.setAttribute("style","border: 1px red solid; position: absolute; top: "+a+"px; width: 10px; height: 10px; overflow: hidden; opacity: 0"),l.setAttribute("contentEditable",!0),l.innerHTML="%ME_PASTEBIN%",this.document.body.appendChild(l),this.on(l,"focus",i),this.on(l,"focusin",i),this.on(l,"focusout",i),l.focus(),e.selection.selectNode(l,this.document),this.boundHandlePaste||(this.boundHandlePaste=this.handlePasteBinPaste.bind(this)),this.on(l,"paste",this.boundHandlePaste)},removePasteBin:function(){null!==t&&(e.selection.selectRange(this.document,t),t=null),null!==n&&(n=null);var o=this.getPasteBin();o&&o&&(this.off(o,"focus",i),this.off(o,"focusin",i),this.off(o,"focusout",i),this.off(o,"paste",this.boundHandlePaste),o.parentElement.removeChild(o))},getPasteBin:function(){return this.document.getElementById(this.pasteBinId)},getPasteBinHtml:function(){var e=this.getPasteBin();if(!e)return!1;if(e.firstChild&&"mcepastebin"===e.firstChild.id)return!1;var t=e.innerHTML;return!(!t||"%ME_PASTEBIN%"===t)&&t},cleanPaste:function(e){var t,n,i,o,r=/]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g),""],[new RegExp(/|/g),""],[new RegExp(/
    $/i),""],[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi),""],[new RegExp(/<\/b>(]*>)?$/gi),""],[new RegExp(/\s+<\/span>/g)," "],[new RegExp(/
    /g),"
    "],[new RegExp(/]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi),''],[new RegExp(/]*font-style:italic[^>]*>/gi),''],[new RegExp(/]*font-weight:(bold|700)[^>]*>/gi),''],[new RegExp(/<(\/?)(i|b|a)>/gi),"<$1$2>"],[new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi),''],[new RegExp(/<\/p>\n+/gi),"

    "],[new RegExp(/\n+

    /gi),""],[new RegExp(/(((?!/gi),"$1"]],this.cleanReplacements||[]);for(t=0;t"+e.split("

    ").join("

    ")+"

    ",n=i.querySelectorAll("a,p,div,br"),t=0;t"+i.innerHTML+"
    ":o.innerHTML=i.innerHTML,i.parentNode.replaceChild(o,i);for(r=t.querySelectorAll("span"),n=0;n0&&(i[0].classList.add(this.firstButtonClass),i[i.length-1].classList.add(this.lastButtonClass)),a},destroy:function(){this.toolbar&&(this.toolbar.parentNode&&this.toolbar.parentNode.removeChild(this.toolbar),delete this.toolbar)},getInteractionElements:function(){return this.getToolbarElement()},getToolbarElement:function(){return this.toolbar||(this.toolbar=this.createToolbar()),this.toolbar},getToolbarActionsElement:function(){return this.getToolbarElement().querySelector(".medium-editor-toolbar-actions")},initThrottledMethods:function(){this.throttledPositionToolbar=e.util.throttle(function(){this.base.isActive&&this.positionToolbarIfShown()}.bind(this))},attachEventHandlers:function(){this.subscribe("blur",this.handleBlur.bind(this)),this.subscribe("focus",this.handleFocus.bind(this)),this.subscribe("editableClick",this.handleEditableClick.bind(this)),this.subscribe("editableKeyup",this.handleEditableKeyup.bind(this)),this.on(this.document.documentElement,"mouseup",this.handleDocumentMouseup.bind(this)),this.static&&this.sticky&&this.on(this.window,"scroll",this.handleWindowScroll.bind(this),!0),this.on(this.window,"resize",this.handleWindowResize.bind(this))},handleWindowScroll:function(){this.positionToolbarIfShown()},handleWindowResize:function(){this.throttledPositionToolbar()},handleDocumentMouseup:function(t){if(t&&t.target&&e.util.isDescendant(this.getToolbarElement(),t.target))return!1;this.checkState()},handleEditableClick:function(){setTimeout(function(){this.checkState()}.bind(this),0)},handleEditableKeyup:function(){this.checkState()},handleBlur:function(){clearTimeout(this.hideTimeout),clearTimeout(this.delayShowTimeout),this.hideTimeout=setTimeout(function(){this.hideToolbar()}.bind(this),1)},handleFocus:function(){this.checkState()},isDisplayed:function(){return this.getToolbarElement().classList.contains("medium-editor-toolbar-active")},showToolbar:function(){clearTimeout(this.hideTimeout),this.isDisplayed()||(this.getToolbarElement().classList.add("medium-editor-toolbar-active"),this.trigger("showToolbar",{},this.base.getFocusedElement()))},hideToolbar:function(){this.isDisplayed()&&(this.getToolbarElement().classList.remove("medium-editor-toolbar-active"),this.trigger("hideToolbar",{},this.base.getFocusedElement()))},isToolbarDefaultActionsDisplayed:function(){return"block"===this.getToolbarActionsElement().style.display},hideToolbarDefaultActions:function(){this.isToolbarDefaultActionsDisplayed()&&(this.getToolbarActionsElement().style.display="none")},showToolbarDefaultActions:function(){this.hideExtensionForms(),this.isToolbarDefaultActionsDisplayed()||(this.getToolbarActionsElement().style.display="block"),this.delayShowTimeout=this.base.delay(function(){this.showToolbar()}.bind(this))},hideExtensionForms:function(){this.forEachExtension(function(e){e.hasForm&&e.isDisplayed()&&e.hideForm()})},multipleBlockElementsSelected:function(){var t=new RegExp("<("+e.util.blockContainerElementNames.join("|")+")[^>]*>","g"),n=e.selection.getSelectionHtml(this.document).replace(/<[^\/>][^>]*><\/[^>]+>/gim,"").match(t);return!!n&&n.length>1},modifySelection:function(){var t=this.window.getSelection().getRangeAt(0);if(this.standardizeSelectionStart&&t.startContainer.nodeValue&&t.startOffset===t.startContainer.nodeValue.length){var n=e.util.findAdjacentTextNodeWithContent(e.selection.getSelectionElement(this.window),t.startContainer,this.document);if(n){for(var i=0;0===n.nodeValue.substr(i,1).trim().length;)i+=1;t=e.selection.select(this.document,n,i,t.endContainer,t.endOffset)}}},checkState:function(){if(!this.base.preventSelectionUpdates){if(!this.base.getFocusedElement()||e.selection.selectionInContentEditableFalse(this.window))return this.hideToolbar();var t=e.selection.getSelectionElement(this.window);return!t||-1===this.getEditorElements().indexOf(t)||t.getAttribute("data-disable-toolbar")?this.hideToolbar():this.updateOnEmptySelection&&this.static?this.showAndUpdateToolbar():!e.selection.selectionContainsContent(this.document)||!1===this.allowMultiParagraphSelection&&this.multipleBlockElementsSelected()?this.hideToolbar():void this.showAndUpdateToolbar()}},showAndUpdateToolbar:function(){this.modifySelection(),this.setToolbarButtonStates(),this.trigger("positionToolbar",{},this.base.getFocusedElement()),this.showToolbarDefaultActions(),this.setToolbarPosition()},setToolbarButtonStates:function(){this.forEachExtension(function(e){"function"==typeof e.isActive&&"function"==typeof e.setInactive&&e.setInactive()}),this.checkActiveButtons()},checkActiveButtons:function(){var t,n=[],i=null,o=e.selection.getSelectionRange(this.document),r=function(e){"function"==typeof e.checkState?e.checkState(t):"function"==typeof e.isActive&&"function"==typeof e.isAlreadyApplied&&"function"==typeof e.setActive&&!e.isActive()&&e.isAlreadyApplied(t)&&e.setActive()};if(o&&(this.forEachExtension(function(e){"function"!=typeof e.queryCommandState||null===(i=e.queryCommandState())?n.push(e):i&&"function"==typeof e.setActive&&e.setActive()}),t=e.selection.getSelectedParentElement(o),this.getEditorElements().some(function(n){return e.util.isDescendant(n,t,!0)})))for(;t&&(n.forEach(r),!e.util.isMediumEditorElement(t));)t=t.parentNode},positionToolbarIfShown:function(){this.isDisplayed()&&this.setToolbarPosition()},setToolbarPosition:function(){var e=this.base.getFocusedElement(),t=this.window.getSelection();if(!e)return this;!this.static&&t.isCollapsed||(this.showToolbar(),this.relativeContainer||(this.static?this.positionStaticToolbar(e):this.positionToolbar(t)),this.trigger("positionedToolbar",{},this.base.getFocusedElement()))},positionStaticToolbar:function(e){this.getToolbarElement().style.left="0";var t,n=this.document.documentElement&&this.document.documentElement.scrollTop||this.document.body.scrollTop,i=this.window.innerWidth,o=this.getToolbarElement(),r=e.getBoundingClientRect(),s=r.top+n,a=r.left+r.width/2,l=o.offsetHeight,c=o.offsetWidth,d=c/2;switch(this.sticky?n>s+e.offsetHeight-l-this.stickyTopOffset?(o.style.top=s+e.offsetHeight-l+"px",o.classList.remove("medium-editor-sticky-toolbar")):n>s-l-this.stickyTopOffset?(o.classList.add("medium-editor-sticky-toolbar"),o.style.top=this.stickyTopOffset+"px"):(o.classList.remove("medium-editor-sticky-toolbar"),o.style.top=s-l+"px"):o.style.top=s-l+"px",this.align){case"left":t=r.left;break;case"right":t=r.right-c;break;case"center":t=a-d}t<0?t=0:t+c>i&&(t=i-Math.ceil(c)-1),o.style.left=t+"px"},positionToolbar:function(e){this.getToolbarElement().style.left="0",this.getToolbarElement().style.right="initial";var t=e.getRangeAt(0),n=t.getBoundingClientRect();(!n||0===n.height&&0===n.width&&t.startContainer===t.endContainer)&&(n=1===t.startContainer.nodeType&&t.startContainer.querySelector("img")?t.startContainer.querySelector("img").getBoundingClientRect():t.startContainer.getBoundingClientRect());var i,o,r=this.window.innerWidth,s=this.getToolbarElement(),a=s.offsetHeight,l=s.offsetWidth/2,c=this.diffLeft-l,d=this.getEditorOption("elementsContainer"),u={},h={};["absolute","fixed"].indexOf(window.getComputedStyle(d).getPropertyValue("position"))>-1?(o=d.getBoundingClientRect(),["top","left"].forEach(function(e){h[e]=n[e]-o[e]}),h.width=n.width,h.height=n.height,n=h,r=o.width,u.top=d.scrollTop):u.top=this.window.pageYOffset,i=n.left+n.width/2,u.top+=n.top-a,n.top<50?(s.classList.add("medium-toolbar-arrow-over"),s.classList.remove("medium-toolbar-arrow-under"),u.top+=50+n.height-this.diffTop):(s.classList.add("medium-toolbar-arrow-under"),s.classList.remove("medium-toolbar-arrow-over"),u.top+=this.diffTop),i'),n.onload=function(){var e=this.document.getElementById(i);e&&(e.removeAttribute("id"),e.removeAttribute("class"),e.src=n.result)}.bind(this))}.bind(this)),t.target.classList.remove("medium-editor-dragover")}}),e.extensions.imageDragging=u,function(){function t(t,n){if(this.options.disableReturn||n.getAttribute("data-disable-return"))t.preventDefault();else if(this.options.disableDoubleReturn||n.getAttribute("data-disable-double-return")){var i=e.selection.getSelectionStart(this.options.ownerDocument);(i&&""===i.textContent.trim()&&"li"!==i.nodeName.toLowerCase()||i.previousElementSibling&&"br"!==i.previousElementSibling.nodeName.toLowerCase()&&""===i.previousElementSibling.textContent.trim())&&t.preventDefault()}}function n(t){var n,i=e.selection.getSelectionStart(this.options.ownerDocument),o=i.nodeName.toLowerCase(),r=/^(\s+|)?$/i,s=/h\d/i;e.util.isKey(t,[e.util.keyCode.BACKSPACE,e.util.keyCode.ENTER])&&i.previousElementSibling&&s.test(o)&&0===e.selection.getCaretOffsets(i).left?e.util.isKey(t,e.util.keyCode.BACKSPACE)&&r.test(i.previousElementSibling.innerHTML)?(i.previousElementSibling.parentNode.removeChild(i.previousElementSibling),t.preventDefault()):!this.options.disableDoubleReturn&&e.util.isKey(t,e.util.keyCode.ENTER)&&((n=this.options.ownerDocument.createElement("p")).innerHTML="
    ",i.previousElementSibling.parentNode.insertBefore(n,i),t.preventDefault()):e.util.isKey(t,e.util.keyCode.DELETE)&&i.nextElementSibling&&i.previousElementSibling&&!s.test(o)&&r.test(i.innerHTML)&&s.test(i.nextElementSibling.nodeName.toLowerCase())?(e.selection.moveCursor(this.options.ownerDocument,i.nextElementSibling),i.previousElementSibling.parentNode.removeChild(i),t.preventDefault()):e.util.isKey(t,e.util.keyCode.BACKSPACE)&&"li"===o&&r.test(i.innerHTML)&&!i.previousElementSibling&&!i.parentElement.previousElementSibling&&i.nextElementSibling&&"li"===i.nextElementSibling.nodeName.toLowerCase()?((n=this.options.ownerDocument.createElement("p")).innerHTML="
    ",i.parentElement.parentElement.insertBefore(n,i.parentElement),e.selection.moveCursor(this.options.ownerDocument,n),i.parentElement.removeChild(i),t.preventDefault()):e.util.isKey(t,e.util.keyCode.BACKSPACE)&&!1!==e.util.getClosestTag(i,"blockquote")&&0===e.selection.getCaretOffsets(i).left?(t.preventDefault(),e.util.execFormatBlock(this.options.ownerDocument,"p")):e.util.isKey(t,e.util.keyCode.ENTER)&&!1!==e.util.getClosestTag(i,"blockquote")&&0===e.selection.getCaretOffsets(i).right?((n=this.options.ownerDocument.createElement("p")).innerHTML="
    ",i.parentElement.insertBefore(n,i.nextSibling),e.selection.moveCursor(this.options.ownerDocument,n),t.preventDefault()):e.util.isKey(t,e.util.keyCode.BACKSPACE)&&e.util.isMediumEditorElement(i.parentElement)&&!i.previousElementSibling&&i.nextElementSibling&&r.test(i.innerHTML)&&(t.preventDefault(),e.selection.moveCursor(this.options.ownerDocument,i.nextSibling),i.parentElement.removeChild(i))}function i(t,n,i){var o=[];if(t||(t=[]),"string"==typeof t&&(t=n.querySelectorAll(t)),e.util.isElement(t)&&(t=[t]),i)for(var r=0;r=0&&(i=e.selection.exportSelection(t,this.options.ownerDocument)),null!==i&&0!==n&&(i.editableElementIndex=n),i},saveSelection:function(){this.selectionState=this.exportSelection()},importSelection:function(t,n){if(t){var i=this.elements[t.editableElementIndex||0];e.selection.importSelection(t,i,this.options.ownerDocument,n)}},restoreSelection:function(){this.importSelection(this.selectionState)},createLink:function(t){var n,i=e.selection.getSelectionElement(this.options.contentWindow),o={};if(-1!==this.elements.indexOf(i)){try{if(this.events.disableCustomEvent("editableInput"),t.url&&e.util.deprecated(".url option for createLink",".value","6.0.0"),(n=t.url||t.value)&&n.trim().length>0){var r=this.options.contentWindow.getSelection();if(r){var s,a,l,c,d=r.getRangeAt(0),u=d.commonAncestorContainer;if(3===d.endContainer.nodeType&&3!==d.startContainer.nodeType&&0===d.startOffset&&d.startContainer.firstChild===d.endContainer&&(u=d.endContainer),a=e.util.getClosestBlockContainer(d.startContainer),l=e.util.getClosestBlockContainer(d.endContainer),3!==u.nodeType&&0!==u.textContent.length&&a===l){var h=a||i,m=this.options.ownerDocument.createDocumentFragment();this.execAction("unlink"),s=this.exportSelection(),m.appendChild(h.cloneNode(!0)),i===h?e.selection.select(this.options.ownerDocument,h.firstChild,0,h.lastChild,3===h.lastChild.nodeType?h.lastChild.nodeValue.length:h.lastChild.childNodes.length):e.selection.select(this.options.ownerDocument,h,0,h,h.childNodes.length);var f=this.exportSelection();0===(c=e.util.findOrCreateMatchingTextNodes(this.options.ownerDocument,m,{start:s.start-f.start,end:s.end-f.start,editableElementIndex:s.editableElementIndex})).length&&((m=this.options.ownerDocument.createDocumentFragment()).appendChild(u.cloneNode(!0)),c=[m.firstChild.firstChild,m.firstChild.lastChild]),e.util.createLink(this.options.ownerDocument,c,n.trim());var p=(m.firstChild.innerHTML.match(/^\s+/)||[""])[0].length;e.util.insertHTMLCommand(this.options.ownerDocument,m.firstChild.innerHTML.replace(/^\s+/,"")),s.start-=p,s.end-=p,this.importSelection(s)}else this.options.ownerDocument.execCommand("createLink",!1,n);this.options.targetBlank||"_blank"===t.target?e.util.setTargetBlank(e.selection.getSelectionStart(this.options.ownerDocument),n):e.util.removeTargetBlank(e.selection.getSelectionStart(this.options.ownerDocument),n),t.buttonClass&&e.util.addClassToAnchors(e.selection.getSelectionStart(this.options.ownerDocument),t.buttonClass)}}if(this.options.targetBlank||"_blank"===t.target||t.buttonClass){(o=this.options.ownerDocument.createEvent("HTMLEvents")).initEvent("input",!0,!0,this.options.contentWindow);for(var g=0,b=this.elements.length;g1?t[1]:"";return{major:parseInt(n[0],10),minor:parseInt(n[1],10),revision:parseInt(n[2],10),preRelease:i,toString:function(){return[n[0],n[1],n[2]].join(".")+(i?"-"+i:"")}}},e.version=e.parseVersionString.call(this,"5.23.3"),e}())}).call(this,n(11)(e),n(91))},3:function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n,i=e[1]||"",o=e[3];if(!o)return i;if(t&&"function"==typeof btoa){var r=(n=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),s=o.sources.map(function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"});return[i].concat(s).concat([r]).join("\n")}return[i].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var i={},o=0;on.parts.length&&(i.parts.length=n.parts.length)}else{var r=[];for(o=0;o=0&&d.splice(t,1)}function g(e){var t=document.createElement("style");return e.attrs.type="text/css",b(t,e.attrs),f(e,t),t}function b(e,t){Object.keys(t).forEach(function(n){e.setAttribute(n,t[n])})}function v(e,t){var n,i,o,r;if(t.transform&&e.css){if(!(r=t.transform(e.css)))return function(){};e.css=r}if(t.singleton){var s=c++;n=l||(l=g(t)),i=E.bind(null,n,s,!1),o=E.bind(null,n,s,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",b(t,e.attrs),f(e,t),t}(t),i=function(e,t,n){var i=n.css,o=n.sourceMap,r=void 0===t.convertToAbsoluteUrls&&o;(t.convertToAbsoluteUrls||r)&&(i=u(i)),o&&(i+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var s=new Blob([i],{type:"text/css"}),a=e.href;e.href=URL.createObjectURL(s),a&&URL.revokeObjectURL(a)}.bind(null,n,t),o=function(){p(n),n.href&&URL.revokeObjectURL(n.href)}):(n=g(t),i=function(e,t){var n=t.css,i=t.media;if(i&&e.setAttribute("media",i),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){p(n)});return i(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;i(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=s()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=m(e,t);return h(n,t),function(e){for(var i=[],o=0;o1)for(var n=1;nn.parts.length&&(r.parts.length=n.parts.length)}else{var s=[];for(i=0;in.parts.length&&(r.parts.length=n.parts.length)}else{var o=[];for(i=0;in.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;owithJson(['data' => example_get_data()]); + } +} diff --git a/public/extensions/custom/endpoints/_directory/endpoints.php b/public/extensions/custom/endpoints/_directory/endpoints.php new file mode 100644 index 0000000000..8c8f589032 --- /dev/null +++ b/public/extensions/custom/endpoints/_directory/endpoints.php @@ -0,0 +1,10 @@ + [ + 'method' => 'GET', + 'handler' => Home::class + ], +]; diff --git a/public/extensions/custom/endpoints/_directory/functions.php b/public/extensions/custom/endpoints/_directory/functions.php new file mode 100644 index 0000000000..cfacaa7e28 --- /dev/null +++ b/public/extensions/custom/endpoints/_directory/functions.php @@ -0,0 +1,14 @@ + [ + 'method' => 'GET', + 'handler' => function (Request $request, Response $response) { + // Simple GET endpoint example + + return $response->withJson([ + 'data' => [ + 'item 1', + 'item 2' + ] + ]); + } + ], + '/datetime' => [ + 'group' => true, + 'endpoints' => [ + '/date[/{when}]' => [ + 'method' => 'GET', + 'handler' => function (Request $request, Response $response) { + $when = $request->getAttribute('when'); + + $datetime = new DateTime(); + switch ($when) { + case 'yesterday': + $datetime->modify('-1 day'); + break; + case 'tomorrow': + $datetime->modify('+1 day'); + break; + default: + // When empty we fallback to 'today' option + if (!empty($when)) { + throw new \Directus\Exception\Exception( + sprintf( + 'Unknown: %. Options available: %s', + $when, implode(['today', 'yesterday', 'tomorrow']) + ) + ); + } + } + + return $response->withJson([ + 'data' => [ + 'date' => $result = $datetime->format('Y-m-d') + ] + ]); + } + ], + '/time' => [ + 'handler' => function ($request, $response) { + return $response->withJSON([ + 'data' => [ + 'time' => date('H:i:s', time()) + ] + ]); + } + ] + ] + ] +]; diff --git a/public/extensions/custom/extensions/.htaccess b/public/extensions/custom/extensions/.htaccess new file mode 100644 index 0000000000..c2a6262ae9 --- /dev/null +++ b/public/extensions/custom/extensions/.htaccess @@ -0,0 +1,5 @@ +# Prevent directory listing +IndexIgnore * + +# Prevent direct access to extension api end-points +RedirectMatch 403 [^/]+/api\.php$ \ No newline at end of file diff --git a/public/extensions/custom/hashers/_CustomHasher.php b/public/extensions/custom/hashers/_CustomHasher.php new file mode 100644 index 0000000000..528743186f --- /dev/null +++ b/public/extensions/custom/hashers/_CustomHasher.php @@ -0,0 +1,23 @@ + $letter) { + $letter = ord($letter) + $i + 1; + $letters[$i] = chr($letter); + } + + return implode('', $letters); + } +} diff --git a/public/extensions/custom/hooks/_products/BeforeInsertProducts.php b/public/extensions/custom/hooks/_products/BeforeInsertProducts.php new file mode 100644 index 0000000000..f427d37a21 --- /dev/null +++ b/public/extensions/custom/hooks/_products/BeforeInsertProducts.php @@ -0,0 +1,23 @@ +set('sku', 'value'); + + // make sure to return the payload + return $payload; + } +} diff --git a/public/extensions/custom/hooks/_products/hooks.php b/public/extensions/custom/hooks/_products/hooks.php new file mode 100644 index 0000000000..85dfed9cba --- /dev/null +++ b/public/extensions/custom/hooks/_products/hooks.php @@ -0,0 +1,7 @@ + [ + 'collection.insert:before' => new \Directus\Custom\Hooks\Products\BeforeInsertProducts() + ] +]; diff --git a/public/extensions/custom/hooks/_webhook/hooks.php b/public/extensions/custom/hooks/_webhook/hooks.php new file mode 100644 index 0000000000..fd661905a8 --- /dev/null +++ b/public/extensions/custom/hooks/_webhook/hooks.php @@ -0,0 +1,21 @@ + [ + // Send an alert when a post is created + 'collection.insert.posts' => function (array $data) { + $client = new \GuzzleHttp\Client([ + 'base_uri' => 'http://example.com' + ]); + + $data = [ + 'type' => 'post', + 'data' => $data + ]; + + $response = $client->request('POST', 'alert', [ + 'json' => $data + ]); + } + ] +]; diff --git a/public/extensions/custom/mail/.gitkeep b/public/extensions/custom/mail/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/extensions/custom/migrations/.gitkeep b/public/extensions/custom/migrations/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000000..2611b6885a --- /dev/null +++ b/public/index.php @@ -0,0 +1,5 @@ +run(); diff --git a/public/storage/uploads/.htaccess b/public/storage/uploads/.htaccess new file mode 100644 index 0000000000..7668a56ee2 --- /dev/null +++ b/public/storage/uploads/.htaccess @@ -0,0 +1,15 @@ + + ExpiresActive On + ExpiresDefault "access 1 year" + + + + ForceType text/plain + + +# Respond with 404 if the file doesn't exists +# Before the API mod_rewrite catches the request + + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule .* - [L,R=404] + diff --git a/public/storage/uploads/00000000001.jpg b/public/storage/uploads/00000000001.jpg new file mode 100644 index 0000000000..63bcba2fb9 Binary files /dev/null and b/public/storage/uploads/00000000001.jpg differ diff --git a/public/thumbnail/.htaccess b/public/thumbnail/.htaccess new file mode 100644 index 0000000000..6b419793d5 --- /dev/null +++ b/public/thumbnail/.htaccess @@ -0,0 +1,2 @@ +RewriteEngine On +RewriteRule (.*) index.php [L] diff --git a/public/thumbnail/img-not-found.png b/public/thumbnail/img-not-found.png new file mode 100644 index 0000000000..3c53c3374c Binary files /dev/null and b/public/thumbnail/img-not-found.png differ diff --git a/public/thumbnail/index.php b/public/thumbnail/index.php new file mode 100644 index 0000000000..bcaf7d5c1d --- /dev/null +++ b/public/thumbnail/index.php @@ -0,0 +1,86 @@ + [ + 'error' => 8, + 'message' => 'API Environment Configuration Not Found: ' . $env + ] + ]); + exit; +} + +$settings = get_kv_directus_settings('thumbnail'); +$timeToLive = array_get($settings, 'cache_ttl', 86400); +try { + // if the thumb already exists, return it + $thumbnailer = new Thumbnailer( + $env, + $app->getContainer()->get('filesystem'), + $app->getContainer()->get('filesystem_thumb'), + $settings, + get_virtual_path() + ); + + $image = $thumbnailer->get(); + + if (!$image) { + // now we can create the thumb + switch ($thumbnailer->action) { + // http://image.intervention.io/api/resize + case 'contain': + $image = $thumbnailer->contain(); + break; + // http://image.intervention.io/api/fit + case 'crop': + default: + $image = $thumbnailer->crop(); + } + } + + header('HTTP/1.1 200 OK'); + header('Content-type: ' . $thumbnailer->getThumbnailMimeType()); + header("Pragma: cache"); + header('Cache-Control: max-age=' . $timeToLive); + header('Last-Modified: '. gmdate('D, d M Y H:i:s \G\M\T', time())); + header('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', time() + $timeToLive)); + echo $image; + exit(0); +} + +catch (Exception $e) { + $filePath = ArrayUtils::get($settings, 'not_found_location'); + if (is_string($filePath) && !empty($filePath) && $filePath[0] !== '/') { + $filePath = $basePath . '/' . $filePath; + } + + // TODO: Throw message if the error is a invalid configuration + if (file_exists($filePath)) { + $mime = image_type_to_mime_type(exif_imagetype($filePath)); + + // TODO: Do we need to cache non-existing files? + header('Content-type: ' . $mime); + header("Pragma: cache"); + header('Cache-Control: max-age=' . $timeToLive); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', time())); + header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + $timeToLive)); + echo file_get_contents($filePath); + } else { + http_response_code(404); + } + + exit(0); +} diff --git a/src/core/Directus/Application/Application.php b/src/core/Directus/Application/Application.php new file mode 100644 index 0000000000..98333bb71e --- /dev/null +++ b/src/core/Directus/Application/Application.php @@ -0,0 +1,225 @@ +createConfig($config); + $container = new Container($container); + + static::$instance = $this; + + parent::__construct($container); + + $this->setBasePath($basePath); + } + + /** + * Gets the application instance (singleton) + * + * This is a temporary solution until we get rid of the Bootstrap object + * + * @return $this + */ + public static function getInstance() + { + return static::$instance; + } + + /** + * Gets an item from the application container + * + * @param string $key + * + * @return mixed + */ + public function fromContainer($key) + { + return $this->getContainer()->get($key); + } + + /** + * Sets the application base path + * + * @param $path + */ + public function setBasePath($path) + { + $this->basePath = rtrim($path, '/'); + + $this->updatePaths(); + } + + protected function updatePaths() + { + $container = $this->getContainer(); + $container['path_base'] = $this->basePath; + } + + /** + * Application configuration object + * + * @return Config + */ + public function getConfig() + { + return $this->getContainer()->get('config'); + } + + /** + * Sets the function that checks the requirements + * + * @param \Closure $function + */ + public function setCheckRequirementsFunction(\Closure $function) + { + $this->checkRequirementsFunction = $function; + } + + /** + * Creates the user configuration based on its configuration + * + * Mainly just separating the Slim settings with the Directus settings and adding paths + * + * @param array $appConfig + * + * @return array + */ + protected function createConfig(array $appConfig) + { + return [ + 'settings' => ArrayUtils::pull($appConfig, 'settings', []), + 'config' => function () use ($appConfig) { + return new Config($appConfig); + } + ]; + } + + /** + * @inheritdoc + */ + public function run($silent = false) + { + if (!$this->booted) { + $this->boot(); + } + + return parent::run($silent); + } + + public function boot() + { + if (!$this->booted) { + // foreach ($this->providers as $provider) { + // $provider->boot($this); + // } + + $this->booted = true; + } + } + + /** + * Get the Directus Version + * + * @return string + */ + public function getVersion() + { + return static::DIRECTUS_VERSION; + } + + /** + * Trigger Filter by name with its payload + * + * @param $name + * @param $payload + * + * @return mixed + */ + public function triggerFilter($name, $payload) + { + return $this->getContainer()->get('hook_emitter')->apply($name, $payload); + } + + /** + * Trigger given action name + * + * @param $name + * @param $params + * + * @return void + */ + public function triggerAction($name, $params = []) + { + if (!is_array($params)) { + $params = [$params]; + } + + array_unshift($params, $name); + + call_user_func_array([$this->getContainer()->get('hook_emitter'), 'run'], $params); + } + + /** + * Calls the given callable if there are missing requirements + * + * @param callable $callback + */ + public function onMissingRequirements(Callable $callback) + { + $errors = $this->checkRequirementsFunction + ? call_user_func($this->checkRequirementsFunction) + : get_missing_requirements(); + + if ($errors) { + $callback($errors); + } + } +} diff --git a/src/core/Directus/Application/Container.php b/src/core/Directus/Application/Container.php new file mode 100644 index 0000000000..3976c75b7e --- /dev/null +++ b/src/core/Directus/Application/Container.php @@ -0,0 +1,59 @@ + '1.1', + 'responseChunkSize' => 4096, + 'outputBuffering' => 'append', + 'determineRouteBeforeAppMiddleware' => false, + 'displayErrorDetails' => false, + 'addContentLengthHeader' => true, + 'routerCacheFile' => false, + ]; + + /** + * @inheritdoc + */ + public function __construct(array $values = []) + { + parent::__construct($values); + + $userSettings = isset($values['settings']) ? $values['settings'] : []; + $this->registerDefaultProviders($userSettings); + $this->registerCoreProviders(); + } + + protected function registerDefaultProviders(array $userSettings) + { + $defaultSettings = $this->defaultSettings; + + /** + * This service MUST return an array or an + * instance of \ArrayAccess. + * + * @return array|\ArrayAccess + */ + $this['settings'] = function () use ($userSettings, $defaultSettings) { + return new Collection(array_merge($defaultSettings, $userSettings)); + }; + + $defaultProvider = new DefaultServicesProvider(); + $defaultProvider->register($this); + } + + protected function registerCoreProviders() + { + $coreProviders = new CoreServicesProvider(); + $coreProviders->register($this); + } +} diff --git a/src/core/Directus/Application/CoreServicesProvider.php b/src/core/Directus/Application/CoreServicesProvider.php new file mode 100644 index 0000000000..8a888ffe1c --- /dev/null +++ b/src/core/Directus/Application/CoreServicesProvider.php @@ -0,0 +1,1199 @@ +getDatabase(); + $container['logger'] = $this->getLogger(); + $container['hook_emitter'] = $this->getEmitter(); + $container['auth'] = $this->getAuth(); + $container['external_auth'] = $this->getExternalAuth(); + $container['session'] = $this->getSession(); + $container['acl'] = $this->getAcl(); + $container['errorHandler'] = $this->getErrorHandler(); + $container['phpErrorHandler'] = $this->getErrorHandler(); + $container['schema_adapter'] = $this->getSchemaAdapter(); + $container['schema_manager'] = $this->getSchemaManager(); + $container['schema_factory'] = $this->getSchemaFactory(); + $container['hash_manager'] = $this->getHashManager(); + $container['embed_manager'] = $this->getEmbedManager(); + $container['filesystem'] = $this->getFileSystem(); + $container['filesystem_thumb'] = $this->getThumbFilesystem(); + $container['files'] = $this->getFiles(); + $container['mailer_transport'] = $this->getMailerTransportManager(); + $container['mailer'] = $this->getMailer(); + $container['mail_view'] = $this->getMailView(); + $container['app_settings'] = $this->getSettings(); + $container['status_mapping'] = $this->getStatusMapping(); + + // Move this separately to avoid clogging one class + $container['cache'] = $this->getCache(); + $container['response_cache'] = $this->getResponseCache(); + + $container['services'] = $this->getServices($container); + } + + /** + * @return \Closure + */ + protected function getLogger() + { + /** + * @param Container $container + * @return Logger + */ + $logger = function ($container) { + $logger = new Logger('app'); + $formatter = new LineFormatter(); + $formatter->allowInlineLineBreaks(); + $formatter->includeStacktraces(); + // TODO: Move log configuration outside "slim app" settings + $path = $container->get('path_base') . '/logs'; + $config = $container->get('config'); + if ($config->has('settings.logger.path')) { + $path = $config->get('settings.logger.path'); + } + + $handler = new StreamHandler( + $path . '/debug.' . date('Y-m') . '.log', + Logger::DEBUG, + false + ); + + $handler->setFormatter($formatter); + $logger->pushHandler($handler); + + $handler = new StreamHandler( + $path . '/error.' . date('Y-m') . '.log', + Logger::CRITICAL, + false + ); + + $handler->setFormatter($formatter); + $logger->pushHandler($handler); + + return $logger; + }; + + return $logger; + } + + /** + * @return \Closure + */ + protected function getErrorHandler() + { + /** + * @param Container $container + * + * @return ErrorHandler + */ + $errorHandler = function (Container $container) { + $hookEmitter = $container['hook_emitter']; + + return new ErrorHandler($hookEmitter, [ + 'env' => $container->get('config')->get('app.env', 'development') + ]); + }; + + return $errorHandler; + } + + /** + * @return \Closure + */ + protected function getEmitter() + { + return function (Container $container) { + $emitter = new Emitter(); + $cachePool = $container->get('cache'); + + // TODO: Move this separately, this is temporary while we move things around + $emitter->addFilter('load.relational.onetomany', function (Payload $payload) { + $rows = $payload->getData(); + /** @var Field $column */ + $column = $payload->attribute('column'); + + if ($column->getInterface() !== 'translation') { + return $payload; + } + + $options = $column->getOptions(); + $code = ArrayUtils::get($options, 'languages_code_column', 'id'); + $languagesTable = ArrayUtils::get($options, 'languages_table'); + $languageIdColumn = ArrayUtils::get($options, 'left_column_name'); + + if (!$languagesTable) { + throw new \Exception('Translations language table not defined for ' . $languageIdColumn); + } + + $tableSchema = SchemaService::getCollection($languagesTable); + $primaryKeyColumn = 'id'; + foreach($tableSchema->getColumns() as $column) { + if ($column->isPrimary()) { + $primaryKeyColumn = $column->getName(); + break; + } + } + + $newData = []; + foreach($rows as $row) { + $index = $row[$languageIdColumn]; + if (is_array($row[$languageIdColumn])) { + $index = $row[$languageIdColumn][$code]; + $row[$languageIdColumn] = $row[$languageIdColumn][$primaryKeyColumn]; + } + + $newData[$index] = $row; + } + + $payload->replace($newData); + + return $payload; + }, $emitter::P_HIGH); + + // Cache subscriptions + $emitter->addAction('postUpdate', function (RelationalTableGateway $gateway, $data) use ($cachePool) { + if(isset($data[$gateway->primaryKeyFieldName])) { + $cachePool->invalidateTags(['entity_'.$gateway->getTable().'_'.$data[$gateway->primaryKeyFieldName]]); + } + }); + + $cacheTableTagInvalidator = function ($tableName) use ($cachePool) { + $cachePool->invalidateTags(['table_'.$tableName]); + }; + + foreach (['collection.update:after', 'collection.drop:after'] as $action) { + $emitter->addAction($action, $cacheTableTagInvalidator); + } + + $emitter->addAction('collection.delete:after', function ($tableName, $ids) use ($cachePool){ + foreach ($ids as $id) { + $cachePool->invalidateTags(['entity_'.$tableName.'_'.$id]); + } + }); + + $emitter->addAction('collection.update.directus_permissions:after', function ($data) use($container, $cachePool) { + $acl = $container->get('acl'); + $dbConnection = $container->get('database'); + $privileges = new DirectusPermissionsTableGateway($dbConnection, $acl); + $record = $privileges->fetchById($data['id']); + $cachePool->invalidateTags(['permissions_collection_'.$record['collection']]); + }); + // /Cache subscriptions + + $emitter->addAction('application.error', function ($e) use($container) { + /** @var Logger $logger */ + $logger = $container->get('logger'); + + $logger->error($e); + }); + $emitter->addFilter('response', function (Payload $payload) use ($container) { + /** @var Acl $acl */ + $acl = $container->get('acl'); + if ($acl->isPublic() || !$acl->getUserId()) { + $payload->set('public', true); + } + return $payload; + }); + $emitter->addAction('collection.insert.directus_roles', function ($data) use ($container) { + $acl = $container->get('acl'); + $zendDb = $container->get('database'); + $privilegesTable = new DirectusPermissionsTableGateway($zendDb, $acl); + $privilegesTable->insertPrivilege([ + 'role' => $data['id'], + 'collection' => 'directus_users', + 'create' => 0, + 'read' => 1, + 'update' => 1, + 'delete' => 0, + 'read_field_blacklist' => 'token', + 'write_field_blacklist' => 'group,token' + ]); + }); + $emitter->addFilter('collection.insert:before', function (Payload $payload) use ($container) { + $collectionName = $payload->attribute('collection_name'); + $collection = SchemaService::getCollection($collectionName); + /** @var Acl $acl */ + $acl = $container->get('acl'); + + + if ($dateCreated = $collection->getDateCreatedField()) { + $payload[$dateCreated] = DateTimeUtils::nowInUTC()->toString(); + } + + if ($dateCreated = $collection->getDateModifiedField()) { + $payload[$dateCreated] = DateTimeUtils::nowInUTC()->toString(); + } + + // Directus Users created user are themselves (primary key) + // populating that field will be a duplicated primary key violation + if ($collection->getName() === 'directus_users') { + return $payload; + } + + $userCreated = $collection->getUserCreatedField(); + $userModified = $collection->getUserModifiedField(); + + if ($userCreated) { + $payload[$userCreated->getName()] = $acl->getUserId(); + } + + if ($userModified) { + $payload[$userModified->getName()] = $acl->getUserId(); + } + + return $payload; + }, Emitter::P_HIGH); + $savesFile = function (Payload $payload, $replace = false) use ($container) { + $collectionName = $payload->attribute('collection_name'); + if ($collectionName !== SchemaManager::COLLECTION_FILES) { + return null; + } + + if ($replace === true && !$payload->has('data')) { + return null; + } + + /** @var Acl $auth */ + $acl = $container->get('acl'); + $data = $payload->getData(); + + /** @var \Directus\Filesystem\Files $files */ + $files = $container->get('files'); + + if (array_key_exists('data', $data) && filter_var($data['data'], FILTER_VALIDATE_URL)) { + $dataInfo = $files->getLink($data['data']); + } else { + $dataInfo = $files->getDataInfo($data['data']); + } + + $type = ArrayUtils::get($dataInfo, 'type', ArrayUtils::get($data, 'type')); + + if (strpos($type, 'embed/') === 0) { + $recordData = $files->saveEmbedData($dataInfo); + } else { + $recordData = $files->saveData($payload['data'], $payload['filename'], $replace); + } + + $payload->replace(array_merge($recordData, ArrayUtils::omit($data, 'filename'))); + $payload->remove('data'); + $payload->remove('html'); + if (!$replace) { + $payload->set('upload_user', $acl->getUserId()); + $payload->set('upload_date', DateTimeUtils::nowInUTC()->toString()); + } + }; + $emitter->addFilter('collection.update:before', function (Payload $payload) use ($container, $savesFile) { + $collection = SchemaService::getCollection($payload->attribute('collection_name')); + + /** @var Acl $acl */ + $acl = $container->get('acl'); + if ($dateModified = $collection->getDateModifiedField()) { + $payload[$dateModified] = DateTimeUtils::nowInUTC()->toString(); + } + + if ($userModified = $collection->getUserModifiedField()) { + $payload[$userModified] = $acl->getUserId(); + } + + // NOTE: exclude date_uploaded from updating a file record + if ($collection->getName() === 'directus_files') { + $payload->remove('date_uploaded'); + } + + $savesFile($payload, true); + + return $payload; + }, Emitter::P_HIGH); + $emitter->addFilter('collection.insert:before', function (Payload $payload) use ($savesFile) { + $savesFile($payload, false); + + return $payload; + }); + $addFilesUrl = function ($rows) { + return append_storage_information($rows); + }; + $emitter->addFilter('collection.select.directus_files:before', function (Payload $payload) { + $columns = $payload->get('columns'); + if (!in_array('filename', $columns)) { + $columns[] = 'filename'; + $payload->set('columns', $columns); + } + return $payload; + }); + + // -- Data types ----------------------------------------------------------------------------- + // TODO: improve Parse boolean/json/array almost similar code + $parseArray = function ($decode, $collection, $data) use ($container) { + /** @var SchemaManager $schemaManager */ + $schemaManager = $container->get('schema_manager'); + $collectionObject = $schemaManager->getCollection($collection); + + foreach ($collectionObject->getFields(array_keys($data)) as $field) { + if (!$field->isArray()) { + continue; + } + + $key = $field->getName(); + $value = $data[$key]; + + // NOTE: If the array has value with comma it will be treat as a separate value + // should we encode the commas to "hide" the comma when splitting the values? + if ($decode) { + $value = !is_array($value) ? explode(',', $value) : $value; + } else { + $value = is_array($value) ? implode(',', $value) : $value; + } + + $data[$key] = $value; + } + + return $data; + }; + + $parseBoolean = function ($collection, $data) use ($container) { + /** @var SchemaManager $schemaManager */ + $schemaManager = $container->get('schema_manager'); + $collectionObject = $schemaManager->getCollection($collection); + + foreach ($collectionObject->getFields(array_keys($data)) as $field) { + if (!$field->isBoolean()) { + continue; + } + + $key = $field->getName(); + $data[$key] = boolval($data[$key]); + } + + return $data; + }; + $parseJson = function ($decode, $collection, $data) use ($container) { + /** @var SchemaManager $schemaManager */ + $schemaManager = $container->get('schema_manager'); + $collectionObject = $schemaManager->getCollection($collection); + + foreach ($collectionObject->getFields(array_keys($data)) as $field) { + if (!$field->isJson()) { + continue; + } + + $key = $field->getName(); + $value = $data[$key]; + + if ($decode === true) { + $value = is_string($value) ? json_decode($value) : $value; + } else { + $value = !is_string($value) ? json_encode($value) : $value; + } + + $data[$key] = $value; + } + + return $data; + }; + + $emitter->addFilter('collection.insert:before', function (Payload $payload) use ($parseJson, $parseArray) { + $payload->replace( + $parseJson( + false, + $payload->attribute('collection_name'), + $payload->getData() + ) + ); + + $payload->replace($parseArray(false, $payload->attribute('collection_name'), $payload->getData())); + + return $payload; + }); + $emitter->addFilter('collection.update:before', function (Payload $payload) use ($parseJson, $parseArray) { + $payload->replace( + $parseJson( + false, + $payload->attribute('collection_name'), + $payload->getData() + ) + ); + $payload->replace( + $parseArray( + false, + $payload->attribute('collection_name'), + $payload->getData() + ) + ); + + return $payload; + }); + $emitter->addFilter('collection.select', function (Payload $payload) use ($container, $parseJson, $parseArray, $parseBoolean) { + $rows = $payload->getData(); + $collectionName = $payload->attribute('collection_name'); + /** @var SchemaManager $schemaManager */ + $schemaManager = $container->get('schema_manager'); + $collection = $schemaManager->getCollection($collectionName); + + $hasJsonField = $collection->hasJsonField(); + $hasBooleanField = $collection->hasBooleanField(); + $hasArrayField = $collection->hasArrayField(); + + if (!$hasArrayField && !$hasBooleanField && !$hasJsonField) { + return $payload; + } + + foreach ($rows as $key => $row) { + if ($hasJsonField) { + $row = $parseJson(true, $collectionName, $row); + } + + if ($hasBooleanField) { + $row = $parseBoolean($collectionName, $row); + } + + if ($hasArrayField) { + $row = $parseArray(true, $collectionName, $row); + } + + $rows[$key] = $row; + } + + $payload->replace($rows); + + return $payload; + }); + // ------------------------------------------------------------------------------------------- + // Add file url and thumb url + $emitter->addFilter('collection.select.directus_files', function (Payload $payload) use ($addFilesUrl, $container) { + $rows = $addFilesUrl($payload->getData()); + + $payload->replace($rows); + + return $payload; + }); + $emitter->addFilter('collection.select.directus_users', function (Payload $payload) use ($container) { + $acl = $container->get('acl'); + $rows = $payload->getData(); + $userId = $acl->getUserId(); + foreach ($rows as &$row) { + $omit = [ + 'password' + ]; + // Authenticated user can see their private info + // Admin can see all users private info + if (!$acl->isAdmin() && $userId !== $row['id']) { + $omit = array_merge($omit, [ + 'token', + 'email_notifications', + 'last_access', + 'last_page' + ]); + } + $row = ArrayUtils::omit($row, $omit); + } + $payload->replace($rows); + return $payload; + }); + $hashUserPassword = function (Payload $payload) use ($container) { + if ($payload->has('password')) { + $auth = $container->get('auth'); + $payload['password'] = $auth->hashPassword($payload['password']); + } + return $payload; + }; + // TODO: Merge with hash user password + $onInsertOrUpdate = function (Payload $payload) use ($container) { + /** @var Provider $auth */ + $auth = $container->get('auth'); + $collectionName = $payload->attribute('collection_name'); + + if (SchemaService::isSystemCollection($collectionName)) { + return $payload; + } + + $collection = SchemaService::getCollection($collectionName); + $data = $payload->getData(); + foreach ($data as $key => $value) { + $column = $collection->getField($key); + if (!$column) { + continue; + } + + if ($column->getInterface() === 'password') { + // TODO: Use custom password hashing method + $payload->set($key, $auth->hashPassword($value)); + } + } + + return $payload; + }; + $emitter->addFilter('collection.update.directus_users:before', function (Payload $payload) use ($container) { + $acl = $container->get('acl'); + + if ($acl->getUserId() != $payload->get('id')) { + return $payload; + } + + if (!$acl->isAdmin() || !$acl->canUpdate('directus_users')) { + throw new ForbiddenException('you are not allowed to update your user information'); + } + + return $payload; + }); + $emitter->addFilter('collection.insert.directus_users:before', $hashUserPassword); + $emitter->addFilter('collection.update.directus_users:before', $hashUserPassword); + $emitter->addFilter('collection.insert.directus_users:before', function (Payload $payload) { + // generate an external id if none is passed + $payload->set('external_id', uniqid($payload->get('email') . '-')); + + return $payload; + }); + $emitter->addFilter('collection.insert.directus_roles:before', function (Payload $payload) { + // generate an external id if none is passed + $payload->set('external_id', uniqid($payload->get('name') . '-')); + + return $payload; + }); + // Hash value to any non system table password interface column + $emitter->addFilter('collection.insert:before', $onInsertOrUpdate); + $emitter->addFilter('collection.update:before', $onInsertOrUpdate); + $preventUsePublicGroup = function (Payload $payload) use ($container) { + $data = $payload->getData(); + if (!ArrayUtils::has($data, 'role')) { + return $payload; + } + + $roleId = ArrayUtils::get($data, 'role'); + if (is_array($roleId)) { + $roleId = ArrayUtils::get($roleId, 'id'); + } + + if (!$roleId) { + return $payload; + } + + $zendDb = $container->get('database'); + $acl = $container->get('acl'); + $tableGateway = new BaseTableGateway(SchemaManager::COLLECTION_ROLES, $zendDb, $acl); + $row = $tableGateway->select(['id' => $roleId])->current(); + if (strtolower($row->name) === 'public') { + throw new ForbiddenException('Users cannot be added into the public group'); + } + + return $payload; + }; + $emitter->addFilter('collection.insert.directus_user_roles:before', $preventUsePublicGroup); + $emitter->addFilter('collection.update.directus_user_roles:before', $preventUsePublicGroup); + $beforeSavingFiles = function ($payload) use ($container) { + $acl = $container->get('acl'); + if (!$acl->canUpdate('directus_files')) { + throw new ForbiddenException('You are not allowed to upload, edit or delete files'); + } + + return $payload; + }; + $emitter->addAction('files.saving', $beforeSavingFiles); + $emitter->addAction('files.thumbnail.saving', $beforeSavingFiles); + // TODO: Make insert actions and filters + $emitter->addFilter('collection.insert.directus_files:before', $beforeSavingFiles); + $emitter->addFilter('collection.update.directus_files:before', $beforeSavingFiles); + $emitter->addFilter('collection.delete.directus_files:before', $beforeSavingFiles); + + return $emitter; + }; + } + + /** + * @return \Closure + */ + protected function getDatabase() + { + return function (Container $container) { + $config = $container->get('config'); + $dbConfig = $config->get('database'); + + // TODO: enforce/check required params + + $charset = ArrayUtils::get($dbConfig, 'charset', 'utf8mb4'); + + $dbConfig = [ + 'driver' => 'Pdo_' . $dbConfig['type'], + 'host' => $dbConfig['host'], + 'port' => $dbConfig['port'], + 'database' => $dbConfig['name'], + 'username' => $dbConfig['username'], + 'password' => $dbConfig['password'], + 'charset' => $charset, + \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, + \PDO::MYSQL_ATTR_INIT_COMMAND => sprintf('SET NAMES "%s"', $charset) + ]; + + try { + $db = new Connection($dbConfig); + $db->connect(); + } catch (\Exception $e) { + throw new ConnectionFailedException($e); + } + + return $db; + }; + } + + /** + * @return \Closure + */ + protected function getAuth() + { + return function (Container $container) { + $db = $container->get('database'); + + return new Provider( + new UserTableGatewayProvider( + new DirectusUsersTableGateway($db) + ), + [ + 'secret_key' => $container->get('config')->get('auth.secret_key') + ] + ); + }; + } + + /** + * @return \Closure + */ + protected function getExternalAuth() + { + return function (Container $container) { + $config = $container->get('config'); + $providersConfig = $config->get('auth.social_providers', []); + + $socialAuth = new Social(); + + $coreSso = get_custom_x('auth', 'public/extensions/core/auth', true); + $customSso = get_custom_x('auth', 'public/extensions/custom/auth', true); + + // Flag the customs providers in order to choose the correct path for the icons + $customSso = array_map(function ($config) { + $config['custom'] = true; + + return $config; + }, $customSso); + + $ssoProviders = array_merge($coreSso, $customSso); + foreach ($providersConfig as $providerName => $providerConfig) { + if (!is_array($providerConfig)) { + continue; + } + + if (ArrayUtils::get($providerConfig, 'enabled') === false) { + continue; + } + + if (array_key_exists($providerName, $ssoProviders) && isset($ssoProviders[$providerName]['provider'])) { + $providerInfo = $ssoProviders[$providerName]; + $class = array_get($providerInfo, 'provider'); + $custom = array_get($providerInfo, 'custom'); + + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Class %s not found', $class)); + } + + $socialAuth->register($providerName, new $class($container, array_merge([ + 'custom' => $custom, + 'callback_url' => get_url('/_/auth/sso/' . $providerName . '/callback') + ], $providerConfig))); + } + } + + return $socialAuth; + }; + } + + /** + * @return \Closure + */ + protected function getSession() + { + return function (Container $container) { + $config = $container->get('config'); + + $session = new Session(new NativeSessionStorage($config->get('session', []))); + $session->getStorage()->start(); + + return $session; + }; + } + + /** + * @return \Closure + */ + protected function getAcl() + { + return function (Container $container) { + return new Acl(); + }; + } + + /** + * @return \Closure + */ + protected function getCache() + { + return function (Container $container) { + $config = $container->get('config'); + $poolConfig = $config->get('cache.pool'); + + if (!$poolConfig || (!is_object($poolConfig) && empty($poolConfig['adapter']))) { + $poolConfig = ['adapter' => 'void']; + } + + if (is_object($poolConfig) && $poolConfig instanceof PhpCachePool) { + $pool = $poolConfig; + } else { + if (!in_array($poolConfig['adapter'], ['apc', 'apcu', 'array', 'filesystem', 'memcached', 'redis', 'void'])) { + throw new \Exception("Valid cache adapters are 'apc', 'apcu', 'filesystem', 'memcached', 'redis'"); + } + + $pool = new VoidCachePool(); + + $adapter = $poolConfig['adapter']; + + if ($adapter == 'apc') { + $pool = new ApcCachePool(); + } + + if ($adapter == 'apcu') { + $pool = new ApcuCachePool(); + } + + if ($adapter == 'array') { + $pool = new ArrayCachePool(); + } + + if ($adapter == 'filesystem') { + if (empty($poolConfig['path']) || !is_string($poolConfig['path'])) { + throw new \Exception('"cache.pool.path parameter is required for "filesystem" adapter and must be a string'); + } + + $cachePath = $poolConfig['path']; + if ($cachePath[0] !== '/') { + $basePath = $container->get('path_base'); + $cachePath = rtrim($basePath, '/') . '/' . $cachePath; + } + + $filesystemAdapter = new Local($cachePath); + $filesystem = new \League\Flysystem\Filesystem($filesystemAdapter); + + $pool = new FilesystemCachePool($filesystem); + } + + if ($adapter == 'memcached') { + $host = (isset($poolConfig['host'])) ? $poolConfig['host'] : 'localhost'; + $port = (isset($poolConfig['port'])) ? $poolConfig['port'] : 11211; + + $client = new \Memcached(); + $client->addServer($host, $port); + $pool = new MemcachedCachePool($client); + } + + if ($adapter == 'redis') { + $host = (isset($poolConfig['host'])) ? $poolConfig['host'] : 'localhost'; + $port = (isset($poolConfig['port'])) ? $poolConfig['port'] : 6379; + + $client = new \Redis(); + $client->connect($host, $port); + $pool = new RedisCachePool($client); + } + } + + return $pool; + }; + } + + /** + * @return \Closure + */ + protected function getSchemaAdapter() + { + return function (Container $container) { + $adapter = $container->get('database'); + $databaseName = $adapter->getPlatform()->getName(); + + switch ($databaseName) { + case 'MySQL': + return new \Directus\Database\Schema\Sources\MySQLSchema($adapter); + // case 'SQLServer': + // return new SQLServerSchema($adapter); + // case 'SQLite': + // return new \Directus\Database\Schemas\Sources\SQLiteSchema($adapter); + // case 'PostgreSQL': + // return new PostgresSchema($adapter); + } + + throw new \Exception('Unknown/Unsupported database: ' . $databaseName); + }; + } + + /** + * @return \Closure + */ + protected function getSchemaManager() + { + return function (Container $container) { + return new SchemaManager( + $container->get('schema_adapter') + ); + }; + } + + /** + * @return \Closure + */ + protected function getSchemaFactory() + { + return function (Container $container) { + return new SchemaFactory( + $container->get('schema_manager') + ); + }; + } + + /** + * @return \Closure + */ + protected function getResponseCache() + { + return function (Container $container) { + return new Response($container->get('cache'), $container->get('config')->get('cache.response_ttl')); + }; + } + + /** + * @return \Closure + */ + protected function getHashManager() + { + return function (Container $container) { + $hashManager = new HashManager(); + $basePath = $container->get('path_base'); + + $path = implode(DIRECTORY_SEPARATOR, [ + $basePath, + 'custom', + 'hashers', + '*.php' + ]); + + $customHashersFiles = glob($path); + $hashers = []; + + if ($customHashersFiles) { + foreach ($customHashersFiles as $filename) { + $name = basename($filename, '.php'); + // filename starting with underscore are skipped + if (StringUtils::startsWith($name, '_')) { + continue; + } + + $hashers[] = '\\Directus\\Custom\\Hasher\\' . $name; + } + } + + foreach ($hashers as $hasher) { + $hashManager->register(new $hasher()); + } + + return $hashManager; + }; + } + + protected function getFileSystem() + { + return function (Container $container) { + $config = $container->get('config'); + + return new Filesystem( + FilesystemFactory::createAdapter($config->get('filesystem'), 'root') + ); + }; + } + + /** + * @return \Closure + */ + protected function getThumbFilesystem() + { + return function (Container $container) { + $config = $container->get('config'); + + return new Filesystem( + FilesystemFactory::createAdapter($config->get('filesystem'), 'root_thumb') + ); + }; + } + + /** + * @return \Closure + */ + protected function getMailerTransportManager() + { + return function (Container $container) { + $config = $container->get('config'); + $manager = new TransportManager(); + + $transports = [ + 'simple_file' => SimpleFileTransport::class, + 'smtp' => \Swift_SmtpTransport::class, + 'sendmail' => \Swift_SendmailTransport::class + ]; + + $mailConfigs = $config->get('mail'); + foreach ($mailConfigs as $name => $mailConfig) { + $transport = ArrayUtils::get($mailConfig, 'transport'); + + if (array_key_exists($transport, $transports)) { + $transport = $transports[$transport]; + } + + $manager->register($name, $transport, $mailConfig); + } + + return $manager; + }; + } + + /** + * @return \Closure + */ + protected function getMailer() + { + return function (Container $container) { + return new Mailer($container->get('mailer_transport')); + }; + } + + /** + * @return \Closure + */ + protected function getSettings() + { + return function (Container $container) { + $dbConnection = $container->get('database'); + $settingsTable = new TableGateway(SchemaManager::COLLECTION_SETTINGS, $dbConnection); + + return $settingsTable->select()->toArray(); + }; + } + + /** + * @return \Closure + */ + protected function getStatusMapping() + { + return function (Container $container) { + $settings = $container->get('app_settings'); + + $statusMapping = []; + foreach ($settings as $setting) { + if ( + ArrayUtils::get($setting, 'scope') == 'status' + && ArrayUtils::get($setting, 'key') == 'status_mapping' + ) { + $statusMapping = json_decode($setting['value'], true); + break; + } + } + + if (!is_array($statusMapping)) { + $statusMapping = []; + } + + return new StatusMapping($statusMapping); + }; + } + + /** + * @return \Closure + */ + protected function getMailView() + { + return function (Container $container) { + $basePath = $container->get('path_base'); + + return new Twig([ + $basePath . '/custom/mail', + $basePath . '/src/mail' + ]); + }; + } + + /** + * @return \Closure + */ + protected function getFiles() + { + return function (Container $container) { + $settings = $container->get('app_settings'); + + // Convert result into a key-value array + $filesSettings = []; + foreach ($settings as $setting) { + if ($setting['scope'] === 'files') { + $filesSettings[$setting['key']] = $setting['value']; + } + } + + $filesystem = $container->get('filesystem'); + $config = $container->get('config'); + $config = $config->get('filesystem', []); + $emitter = $container->get('hook_emitter'); + + return new Files( + $filesystem, + $config, + $filesSettings, + $emitter + ); + }; + } + + protected function getEmbedManager() + { + return function (Container $container) { + $app = Application::getInstance(); + $embedManager = new EmbedManager(); + $acl = $container->get('acl'); + $adapter = $container->get('database'); + + // Fetch files settings + $settingsTableGateway = new DirectusSettingsTableGateway($adapter, $acl); + try { + $settings = $settingsTableGateway->fetchItems([ + 'filter' => ['scope' => 'files'] + ]); + } catch (\Exception $e) { + $settings = []; + /** @var Logger $logger */ + $logger = $container->get('logger'); + $logger->warning($e->getMessage()); + } + + $providers = [ + '\Directus\Embed\Provider\VimeoProvider', + '\Directus\Embed\Provider\YoutubeProvider' + ]; + + $path = implode(DIRECTORY_SEPARATOR, [ + $app->getContainer()->get('path_base'), + 'custom', + 'embeds', + '*.php' + ]); + + $customProvidersFiles = glob($path); + if ($customProvidersFiles) { + foreach ($customProvidersFiles as $filename) { + $providers[] = '\\Directus\\Embed\\Provider\\' . basename($filename, '.php'); + } + } + + foreach ($providers as $providerClass) { + $provider = new $providerClass($settings); + $embedManager->register($provider); + } + + return $embedManager; + }; + } + + /** + * Register all services + * + * @param Container $mainContainer + * + * @return \Closure + * + * @internal param Container $container + * + */ + protected function getServices(Container $mainContainer) + { + // A services container of all Directus services classes + return function () use ($mainContainer) { + $container = new Container(); + + // ============================================================================= + // Register all services + // ----------------------------------------------------------------------------- + // TODO: Set a place to load all the services + // ============================================================================= + $container['auth'] = $this->getAuthService($mainContainer); + + return $container; + }; + } + + /** + * @param Container $container Application container + * + * @return \Closure + */ + protected function getAuthService(Container $container) + { + return function () use ($container) { + return new AuthService($container); + }; + } +} + diff --git a/src/core/Directus/Application/DefaultServicesProvider.php b/src/core/Directus/Application/DefaultServicesProvider.php new file mode 100644 index 0000000000..c2e09939c1 --- /dev/null +++ b/src/core/Directus/Application/DefaultServicesProvider.php @@ -0,0 +1,57 @@ +get('environment')); + }; + + /** + * @param Container $container + * + * @return ResponseInterface + */ + $container['response'] = function ($container) { + $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']); + $response = new Response(200, $headers); + + return $response->withProtocolVersion($container->get('settings')['httpVersion']); + }; + + if (!isset($container['notFoundHandler'])) { + $container['notFoundHandler'] = function () { + return new NotFoundHandler(); + }; + } + + if (!isset($container['notAllowedHandler'])) { + $container['notAllowedHandler'] = function () { + return new MethodNotAllowedHandler(); + }; + } + + parent::register($container); + } +} diff --git a/src/core/Directus/Application/ErrorHandlers/ErrorHandler.php b/src/core/Directus/Application/ErrorHandlers/ErrorHandler.php new file mode 100644 index 0000000000..4b141c9f72 --- /dev/null +++ b/src/core/Directus/Application/ErrorHandlers/ErrorHandler.php @@ -0,0 +1,199 @@ +emitter = $emitter; + $this->settings = $settings; + } + + /** + * Handles the error + * + * @param Request $request + * @param Response $response + * @param \Exception|\Throwable $exception + * + * @return Response + */ + public function __invoke(Request $request, Response $response, $exception) + { + $data = $this->processException($exception); + + if ($this->isMessageSCIM($response)) { + return $response + ->withStatus($data['http_status_code']) + ->withJson([ + 'schemas' => [ScimService::SCHEMA_ERROR], + 'status' => $data['http_status_code'], + 'detail' => $data['error']['message'] + ]); + } + + return $response + ->withStatus($data['http_status_code']) + ->withJson(['error' => $data['error']]); + } + + /** + * Returns an exception error and http status code information + * + * http_status_code and error key returned in the array + * + * @param \Exception|\Throwable $exception + * + * @return array + */ + public function processException($exception) + { + $productionMode = ArrayUtils::get($this->settings, 'env', 'development') === 'production'; + $this->trigger($exception); + + $message = $exception->getMessage() ?: 'Unknown Error'; + $code = null; + // Not showing internal PHP errors (for PHP7) for production + if ($productionMode && $this->isError($exception)) { + $message = 'Internal Server Error'; + } + + if (!$productionMode && ($previous = $exception->getPrevious())) { + $message .= ' ' . $previous->getMessage(); + } + + if ($exception instanceof Exception) { + $code = $exception->getErrorCode(); + } + + $httpStatusCode = 500; + if ($exception instanceof BadRequestExceptionInterface) { + $httpStatusCode = 400; + } else if ($exception instanceof NotFoundExceptionInterface) { + $httpStatusCode = 404; + } else if ($exception instanceof UnauthorizedExceptionInterface) { + $httpStatusCode = 401; + } else if ($exception instanceof ForbiddenException) { + $httpStatusCode = 403; + } else if ($exception instanceof ConflictExceptionInterface) { + $httpStatusCode = 409; + } else if ($exception instanceof UnprocessableEntityExceptionInterface) { + $httpStatusCode = 422; + } + + $data = [ + 'code' => $code, + 'message' => $message + ]; + + if ($exception instanceof InvalidQueryException) { + $data['query'] = $exception->getQuery(); + } + + if (!$productionMode) { + $data = array_merge($data, [ + 'class' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + // Do not output the trace + // it can be so long or complex + // that json_encode fails + 'trace' => $exception->getTrace(), + // maybe as string, but let's get rid of them, for the best + // and look at the logs instead + // 'traceAsString' => $exception->getTraceAsString(), + ]); + } + + return [ + 'http_status_code' => $httpStatusCode, + 'error' => $data + ]; + } + + /** + * Checks whether the exception is an error + * + * @param $exception + * + * @return bool + */ + protected function isError($exception) + { + return $exception instanceof \Error + || $exception instanceof \ErrorException; + } + + /** + * Triggers application error event + * + * @param \Throwable $e + */ + protected function trigger($e) + { + if ($this->emitter) { + $this->emitter->run('application.error', $e); + } + } + + /** + * @param MessageInterface $message + * + * @return mixed|string + */ + protected function isMessageSCIM(MessageInterface $message) + { + $contentType = $message->getHeaderLine('Content-Type'); + + if (preg_match('/scim\+json/', $contentType, $matches)) { + return true; + } + + return false; + } +} diff --git a/src/core/Directus/Application/ErrorHandlers/MethodNotAllowedHandler.php b/src/core/Directus/Application/ErrorHandlers/MethodNotAllowedHandler.php new file mode 100644 index 0000000000..f7393ed77a --- /dev/null +++ b/src/core/Directus/Application/ErrorHandlers/MethodNotAllowedHandler.php @@ -0,0 +1,26 @@ +withStatus(Response::HTTP_METHOD_NOT_ALLOWED) + ->withJson(['error' => [ + 'code' => MethodNotAllowedException::ERROR_CODE, + 'message' => 'Method Not Allowed' + ]]); + } +} diff --git a/src/core/Directus/Application/ErrorHandlers/NotFoundHandler.php b/src/core/Directus/Application/ErrorHandlers/NotFoundHandler.php new file mode 100644 index 0000000000..ffd2c6a215 --- /dev/null +++ b/src/core/Directus/Application/ErrorHandlers/NotFoundHandler.php @@ -0,0 +1,26 @@ +withStatus(Response::HTTP_NOT_FOUND) + ->withJson(['error' => [ + 'code' => NotFoundException::ERROR_CODE, + 'message' => 'Not Found' + ]]); + } +} diff --git a/src/core/Directus/Application/Http/Middleware/AbstractMiddleware.php b/src/core/Directus/Application/Http/Middleware/AbstractMiddleware.php new file mode 100644 index 0000000000..5d540c12c5 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/AbstractMiddleware.php @@ -0,0 +1,19 @@ +container = $container; + } +} diff --git a/src/core/Directus/Application/Http/Middleware/AbstractRateLimitMiddleware.php b/src/core/Directus/Application/Http/Middleware/AbstractRateLimitMiddleware.php new file mode 100644 index 0000000000..c3d1af3426 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/AbstractRateLimitMiddleware.php @@ -0,0 +1,111 @@ +container->get('config'); + + $this->options = new Collection((array) $config->get('rate_limit')); + } + + public function __invoke(Request $request, Response $response, callable $next) + { + if (!$this->isEnabled()) { + return $next($request, $response); + } + + return (new RateLimitMiddleware( + $this->createAdapter(), + $this->getIdentityResolver(), + Options::fromArray([]) + ))->process($request, $response, $next); + } + + /** + * @return bool + */ + public function isEnabled() + { + return $this->options->get('enabled') === true; + } + + /** + * @return string + */ + public function getAdapterName() + { + return $this->options->get('adapter'); + } + + /** + * @return int + */ + public function getLimit() + { + return (int) $this->options->get('limit', 0); + } + + /** + * @return int + */ + public function getInterval() + { + return (int) $this->options->get('interval', 60); + } + + /** + * @return RateLimiterInterface + * + * @throws RuntimeException + */ + public function createAdapter() + { + $limit = $this->getLimit(); + $window = $this->getInterval(); + $adapterName = $this->getAdapterName(); + + switch ($adapterName) { + case 'redis': + $adapter = RateLimiterFactory::createRedisBackedRateLimiter([ + 'host' => $this->options->get('host', '127.0.0.1'), + 'port' => $this->options->get('port', 6379), + 'timeout' => $this->options->get('timeout', 0.0) + ], $limit, $window); + break; + case 'memory': + $adapter = RateLimiterFactory::createInMemoryRateLimiter($limit, $window); + break; + default: + throw new RuntimeException( + sprintf('Unknown Rate limit adapter: "%s"', $adapterName) + ); + } + + return $adapter; + } + + /** + * @return IdentityResolverInterface + */ + abstract protected function getIdentityResolver(); +} diff --git a/src/core/Directus/Application/Http/Middleware/AdminMiddleware.php b/src/core/Directus/Application/Http/Middleware/AdminMiddleware.php new file mode 100644 index 0000000000..25d2208e59 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/AdminMiddleware.php @@ -0,0 +1,23 @@ +container->get('acl'); + + if ($acl->isAdmin()) { + return $next($request, $response); + } + + throw new UserNotAuthenticatedException(); + } +} diff --git a/src/core/Directus/Application/Http/Middleware/AuthenticatedMiddleware.php b/src/core/Directus/Application/Http/Middleware/AuthenticatedMiddleware.php new file mode 100644 index 0000000000..264687fd54 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/AuthenticatedMiddleware.php @@ -0,0 +1,23 @@ +container->get('acl'); + + if ($acl->getUserId() && $acl->isPublic() !== true) { + return $next($request, $response); + } + + throw new UserNotAuthenticatedException(); + } +} diff --git a/src/core/Directus/Application/Http/Middleware/AuthenticationMiddleware.php b/src/core/Directus/Application/Http/Middleware/AuthenticationMiddleware.php new file mode 100644 index 0000000000..763859457b --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/AuthenticationMiddleware.php @@ -0,0 +1,179 @@ +authenticate($request); + $publicRoleId = $this->getPublicRoleId(); + if (!$user && !$publicRoleId) { + throw new UserNotAuthenticatedException(); + } + + if (!$user && $publicRoleId) { + // NOTE: 0 will not represent a "guest" or the "public" user + // To prevent the issue where user column on activity table can't be null + $user = new User([ + 'id' => 0 + ]); + } + + $dbConnection = $this->container->get('database'); + $permissionsTable = new DirectusPermissionsTableGateway($dbConnection, null); + $permissionsByCollection = $permissionsTable->getUserPermissions($user->getId()); + $rolesIpWhitelist = $this->getRolesIPWhitelist(); + + /** @var Acl $acl */ + $acl = $this->container->get('acl'); + $acl->setPermissions($permissionsByCollection); + $acl->setRolesIpWhitelist($rolesIpWhitelist); + // TODO: Adding an user should auto set its ID and GROUP + // TODO: User data should be casted to its data type + // TODO: Make sure that the group is not empty + $acl->setUserId($user->getId()); + if (!$user && $publicRoleId) { + $acl->setPublic($publicRoleId); + } + + if (!$acl->isIpAllowed(get_request_ip())) { + throw new UnauthorizedException('Request not allowed from IP address'); + } + + $hookEmitter = $this->container->get('hook_emitter'); + $hookEmitter->run('directus.authenticated', [$user]); + + return $next($request, $response); + } + + /** + * Tries to authenticate the user based on the HTTP Request + * + * @param Request $request + * + * @return UserInterface + */ + protected function authenticate(Request $request) + { + $user = null; + $authToken = $this->getAuthToken($request); + + if ($authToken) { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + $user = $authService->authenticateWithToken($authToken); + } + + return $user; + } + + /** + * Gets the authentication token from the request + * + * @param Request $request + * + * @return string + */ + protected function getAuthToken(Request $request) + { + $authToken = null; + + if ($request->getParam('access_token')) { + $authToken = $request->getParam('access_token'); + } elseif ($request->hasHeader('Php-Auth-User')) { + $authUser = $request->getHeader('Php-Auth-User'); + $authPassword = $request->getHeader('Php-Auth-Pw'); + + if (is_array($authUser)) { + $authUser = array_shift($authUser); + } + + if (is_array($authPassword)) { + $authPassword = array_shift($authPassword); + } + + if ($authUser && (empty($authPassword) || $authUser === $authPassword)) { + $authToken = $authUser; + } + } elseif ($request->hasHeader('Authorization')) { + $authorizationHeader = $request->getHeader('Authorization'); + + // If there's multiple Authorization header, pick first, ignore the rest + if (is_array($authorizationHeader)) { + $authorizationHeader = array_shift($authorizationHeader); + } + + if (is_string($authorizationHeader) && preg_match("/Bearer\s+(.*)$/i", $authorizationHeader, $matches)) { + $authToken = $matches[1]; + } + } + + return $authToken; + } + + /** + * Gets the public role id if exists + * + * @return int|null + */ + protected function getPublicRoleId() + { + $dbConnection = $this->container->get('database'); + $directusGroupsTableGateway = new TableGateway('directus_roles', $dbConnection); + $publicRole = $directusGroupsTableGateway->select(['name' => 'public'])->current(); + + $roleId = null; + if ($publicRole) { + $roleId = $publicRole['id']; + } + + return $roleId; + } + + /** + * Gets IP whitelist + * + * @return array + */ + protected function getRolesIpWhitelist() + { + $dbConnection = $this->container->get('database'); + $directusGroupsTableGateway = new TableGateway('directus_roles', $dbConnection); + $select = new Select($directusGroupsTableGateway->table); + $select->columns(['id', 'ip_whitelist']); + $select->limit(1); + + $result = $directusGroupsTableGateway->selectWith($select); + + $list = []; + foreach ($result as $row) { + $list[$row['id']] = array_filter(preg_split('/,\s*/', $row['ip_whitelist'])); + } + + return $list; + } +} diff --git a/src/core/Directus/Application/Http/Middleware/CorsMiddleware.php b/src/core/Directus/Application/Http/Middleware/CorsMiddleware.php new file mode 100644 index 0000000000..1167e0558f --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/CorsMiddleware.php @@ -0,0 +1,91 @@ +getOptions(); + if (ArrayUtils::get($corsOptions, 'enabled', false)) { + $this->processHeaders($request, $response); + } + + if (!$request->isOptions()) { + return $next($request, $response); + } + + return $response; + } + + /** + * Sets the headers + * + * @param Request $request + * @param Response $response + */ + protected function processHeaders(Request $request, Response $response) + { + $corsOptions = $this->getOptions(); + $origin = $this->getOrigin($request); + + if ($origin) { + $response->setHeader('Access-Control-Allow-Origin', $origin); + foreach (ArrayUtils::get($corsOptions, 'headers', []) as $name => $value) { + // Support two options: + // 1. [Key, Value] + // 2. Key => Value + if (is_array($value)) { + // using $value will make name the first value character of $value value + $temp = $value; + list($name, $value) = $temp; + } + + $response->setHeader($name, $value); + } + } + } + + /** + * Gets the header origin + * + * This is the origin the header is going to be used + * + * There are four different scenario's for possibly returning an + * Access-Control-Allow-Origin header: + * + * 1) null - don't return header + * 2) '*' - return header '*' + * 3) {str} - return header {str} + * 4) [{str}, {str}, {str}] - if origin matches, return header {str} + * + * @param Request $request + * + * @return string + */ + protected function getOrigin(Request $request) + { + $corsOptions = $this->getOptions(); + $requestOrigin = $request->getOrigin(); + $allowedOrigins = ArrayUtils::get($corsOptions, 'origin', '*'); + + return cors_get_allowed_origin($allowedOrigins, $requestOrigin); + } + + /** + * Gets CORS options + * + * @return array + */ + protected function getOptions() + { + $config = $this->container->get('config'); + + return $config->get('cors', []); + } +} diff --git a/src/core/Directus/Application/Http/Middleware/IpRateLimitMiddleware.php b/src/core/Directus/Application/Http/Middleware/IpRateLimitMiddleware.php new file mode 100644 index 0000000000..5a74b562a7 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/IpRateLimitMiddleware.php @@ -0,0 +1,16 @@ + false, + 'message' => $errorMessage + ]; + $view = $app->view(); + $view->setData('jsonResponse', $jsonResponse); + \Directus\Slim\Middleware::renderJson(); + break; + case 'redirect': + default: + $app->flash('error', $errorMessage); + $app->redirect(ROOT_URL); + } + // How to interrupt, without exiting? + // return false; + // Hmmm interrupts output: + // $app->halt(401); + exit; + } + + public static function refuseUnauthenticatedUsers($responseType = 'redirect') + { + self::validateResponseType($responseType); + return function () use ($responseType) { + if (Auth::loggedIn()) { + return true; + } + $errorMessage = 'You must be logged in to perform that action.'; + \Directus\Slim\Middleware::refuseWithErrorMessage($errorMessage, $responseType); + }; + } + + public static function refuseAuthenticatedUsers($responseType = 'redirect') + { + self::validateResponseType($responseType); + return function () use ($responseType) { + if (!Auth::loggedIn()) { + return true; + } + $errorMessage = 'You must be logged out to perform that action.'; + \Directus\Slim\Middleware::refuseWithErrorMessage($errorMessage, $responseType); + }; + } + + public static function renderJson() + { + $app = \Slim\Slim::getInstance(); + $view = $app->view(); + $viewData = $view->getData(); + if (!array_key_exists('jsonResponse', $viewData)) { + throw new \RuntimeException('renderJson middleware expected `jsonResponse` key within the view data array.'); + } + JsonView::render($viewData['jsonResponse']); + } + +} diff --git a/src/core/Directus/Application/Http/Middleware/RateLimit/UserIdentityResolver.php b/src/core/Directus/Application/Http/Middleware/RateLimit/UserIdentityResolver.php new file mode 100644 index 0000000000..a1d25b0f5c --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/RateLimit/UserIdentityResolver.php @@ -0,0 +1,25 @@ +acl = $acl; + } + + /** + * {@inheritdoc} + */ + public function getIdentity(RequestInterface $request) + { + return $this->acl->getUserId(); + } +} diff --git a/src/core/Directus/Application/Http/Middleware/ResponseCacheMiddleware.php b/src/core/Directus/Application/Http/Middleware/ResponseCacheMiddleware.php new file mode 100644 index 0000000000..6e41fcce15 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/ResponseCacheMiddleware.php @@ -0,0 +1,59 @@ +container; + $forceRefresh = false; + + /** @var CacheResponse $cache */ + $cache = $this->container->get('response_cache'); + + if ($request->isGet()) { + $parameters = $request->getQueryParams(); + ksort($parameters); + + $forceRefresh = (empty($parameters['refresh_cache'])) ? false : true; + unset($parameters['refresh_cache']); + + $requestPath = $request->getUri()->getPath(); + + $key = md5($container->get('acl')->getUserId().'@'.$requestPath.'?'.http_build_query($parameters)); + } else { + $key = null; + } + + $config = $container->get('config'); + if ($config->get('cache.enabled') && $key && !$forceRefresh && $cachedResponse = $cache->get($key)) { + $body = new \Slim\Http\Body(fopen('php://temp', 'r+')); + $body->write($cachedResponse['body']); + $response = $response->withBody($body)->withHeaders($cachedResponse['headers']); + } else { + /** @var Response $response */ + $response = $next($request, $response); + + $body = $response->getBody(); + $body->rewind(); + $bodyContent = $body->getContents(); + $headers = $response->getHeaders(); + + $cache->process($key, $bodyContent, $headers); + } + + return $response; + } +} diff --git a/src/core/Directus/Application/Http/Middleware/TableGatewayMiddleware.php b/src/core/Directus/Application/Http/Middleware/TableGatewayMiddleware.php new file mode 100644 index 0000000000..56e8af1de8 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/TableGatewayMiddleware.php @@ -0,0 +1,41 @@ +container; + + // tablegateway dependency + SchemaService::setAclInstance($container->get('acl')); + SchemaService::setConnectionInstance($container->get('database')); + SchemaService::setConfig($container->get('config')); + BaseTableGateway::setHookEmitter($container->get('hook_emitter')); + BaseTableGateway::setContainer($container); + TableGatewayFactory::setContainer($container); + + $container['app.settings'] = function (Container $container) { + $dbConnection = $container->get('database'); + $DirectusSettingsTableGateway = new \Zend\Db\TableGateway\TableGateway('directus_settings', $dbConnection); + $rowSet = $DirectusSettingsTableGateway->select(); + + $settings = []; + foreach ($rowSet as $setting) { + $settings[$setting['scope']][$setting['key']] = $setting['value']; + } + + return $settings; + }; + + return $next($request, $response); + } +} diff --git a/src/core/Directus/Application/Http/Middleware/UserRateLimitMiddleware.php b/src/core/Directus/Application/Http/Middleware/UserRateLimitMiddleware.php new file mode 100644 index 0000000000..5bb54b1114 --- /dev/null +++ b/src/core/Directus/Application/Http/Middleware/UserRateLimitMiddleware.php @@ -0,0 +1,13 @@ +container->get('acl')); + } +} diff --git a/src/core/Directus/Application/Http/Request.php b/src/core/Directus/Application/Http/Request.php new file mode 100644 index 0000000000..8daf1574a8 --- /dev/null +++ b/src/core/Directus/Application/Http/Request.php @@ -0,0 +1,26 @@ +getHeader('Origin') ?: $this->getServerParam('HTTP_ORIGIN'); + } + + /** + * Returns the Request Referer Url + * + * @return mixed + */ + public function getReferer() + { + return $this->getHeader('Referer') ?: $this->getServerParam('HTTP_REFERER'); + } +} diff --git a/src/core/Directus/Application/Http/Response.php b/src/core/Directus/Application/Http/Response.php new file mode 100644 index 0000000000..d2065a075c --- /dev/null +++ b/src/core/Directus/Application/Http/Response.php @@ -0,0 +1,59 @@ + $value) { + $this->headers->set($name, $value); + } + + return $this; + } + + /** + * Sets key-value header information + * + * @param string $name + * @param mixed $value + * + * @return $this + */ + public function setHeader($name, $value) + { + return $this->withHeaders([$name => $value]); + } + + /** + * Json. + * + * Note: This method is not part of the PSR-7 standard. + * + * This method prepares the response object to return an HTTP Json + * response to the client. + * + * @param mixed $data The data + * @param int $status The HTTP status code. + * @param int $encodingOptions Json encoding options + * @throws \RuntimeException + * @return static + */ + public function withScimJson($data, $status = null, $encodingOptions = 0) + { + $response = $this->withJson($data, $status, $encodingOptions); + + return $response->withHeader('Content-Type', 'application/scim+json;charset=utf-8'); + } +} diff --git a/src/core/Directus/Application/Route.php b/src/core/Directus/Application/Route.php new file mode 100644 index 0000000000..91a8602d37 --- /dev/null +++ b/src/core/Directus/Application/Route.php @@ -0,0 +1,140 @@ +container = $container; + } + + /** + * Convert array data into an API output format + * + * @param Request $request + * @param Response $response + * @param array $data + * @param array $options + * + * @return Response + */ + public function responseWithData(Request $request, Response $response, array $data, array $options = []) + { + $data = $this->getResponseData($request, $response, $data, $options); + + return $response->withJson($data); + } + + /** + * Convert array data into an API output format + * + * @param Request $request + * @param Response $response + * @param array $data + * @param array $options + * + * @return Response + */ + public function responseScimWithData(Request $request, Response $response, array $data, array $options = []) + { + $data = $this->getResponseData($request, $response, $data, $options); + + return $response->withScimJson($data); + } + + /** + * Pass the data through response hook filters + * + * @param Request $request + * @param Response $response + * @param array $data + * @param array $options + * + * @return array|mixed|\stdClass + */ + protected function getResponseData(Request $request, Response $response, array $data, array $options = []) + { + $data = $this->triggerResponseFilter($request, $data, (array) $options); + + // NOTE: when data is a empty array, the output will be an array + // this create problem/confusion as we always return an object + if (empty($data)) { + $data = new \stdClass(); + } + + return $data; + } + + /** + * Trigger a response filter + * + * @param Request $request + * @param array $data + * @param array $options + * + * @return mixed + */ + protected function triggerResponseFilter(Request $request, array $data, array $options = []) + { + $meta = ArrayUtils::get($data, 'meta'); + $method = $request->getMethod(); + + $attributes = [ + 'meta' => $meta, + 'request' => [ + 'path' => $request->getUri()->getPath(), + 'method' => $method + ] + ]; + + /** @var Emitter $emitter */ + $emitter = $this->container->get('hook_emitter'); + + $payload = $emitter->apply('response', $data, $attributes); + $payload = $emitter->apply('response.' . $method, $payload); + + if (isset($meta['table'])) { + $emitter->apply('response.' . $meta['table'], $payload); + $payload = $emitter->apply(sprintf('response.%s.%s', + $meta['table'], + $method + ), $payload); + } + + return $payload; + } + + /** + * Parse the output data + * + * @param Response $response + * @param array $data + * @param array $options + * + * @return Response + */ + public function withData(Response $response, array $data, array $options = []) + { + // TODO: Event parsing output + // This event can guess/change the output from json to xml + + return $response->withJson($data); + } +} diff --git a/src/core/Directus/Authentication/Exception/ExpiredRequestTokenException.php b/src/core/Directus/Authentication/Exception/ExpiredRequestTokenException.php new file mode 100644 index 0000000000..c27f66dfcc --- /dev/null +++ b/src/core/Directus/Authentication/Exception/ExpiredRequestTokenException.php @@ -0,0 +1,15 @@ +attributes['email'] = $email; + $message = sprintf('User with email "%s" not found', $email); + + parent::__construct($message, static::ERROR_CODE); + } +} diff --git a/src/core/Directus/Authentication/Provider.php b/src/core/Directus/Authentication/Provider.php new file mode 100644 index 0000000000..681db1783c --- /dev/null +++ b/src/core/Directus/Authentication/Provider.php @@ -0,0 +1,498 @@ +userProvider = $userProvider; + $this->user = null; + $this->secretKey = $options['secret_key']; + $this->ttl = (int)$ttl; + } + + /** + * @throws UserNotAuthenticatedException + */ + protected function enforceUserIsAuthenticated() + { + if (!$this->check()) { + throw new UserNotAuthenticatedException('Attempting to inspect a non-authenticated user'); + } + } + + /** + * Signing In a user + * + * Creating the user token and resetting previous token + * + * @param array $credentials + * + * @return UserInterface + * + * @throws InvalidUserCredentialsException + * @throws UserInactiveException + */ + public function login(array $credentials) + { + $email = ArrayUtils::get($credentials, 'email'); + $password = ArrayUtils::get($credentials, 'password'); + + $user = null; + if ($email && $password) { + // Verify Credentials + $user = $this->findUserWithCredentials($email, $password); + $this->setUser($user); + } + + return $user; + } + + /** + * Returns a user if the credentials matches + * + * @param string $email + * @param string $password + * + * @return UserInterface + * + * @throws InvalidUserCredentialsException + */ + public function findUserWithCredentials($email, $password) + { + $user = $this->findUserWithEmail($email); + + // Verify that the user has an id (exists), it returns empty user object otherwise + if (!password_verify($password, $user->get('password'))) { + throw new InvalidUserCredentialsException(); + } + + $this->user = $user; + + return $user; + } + + /** + * Returns a user with the given email if exists + * Otherwise throw an UserNotFoundException + * + * @param $email + * + * @return User\User + * + * @throws UserNotFoundException + */ + public function findUserWithEmail($email) + { + $user = $this->userProvider->findByEmail($email); + + if (!$user || !$user->getId()) { + throw new UserNotFoundException(); + } + + return $user; + } + + /** + * Checks if the user is active + * + * @param UserInterface $user + * + * @return bool + */ + public function isActive(UserInterface $user) + { + $userProvider = $this->userProvider; + + // TODO: Cast attributes values + return $user->get('status') == $userProvider::STATUS_ACTIVE; + } + + /** + * Authenticate an user using a JWT Token + * + * @param $token + * + * @return UserInterface + * + * @throws InvalidTokenException + */ + public function authenticateWithToken($token) + { + $payload = JWTUtils::decode($token, $this->getSecretKey(), ['HS256']); + if (!JWTUtils::hasPayloadType(JWTUtils::TYPE_AUTH, $payload)) { + throw new InvalidTokenException(); + } + + $conditions = [ + 'id' => $payload->id, + // 'group' => $payload->group + ]; + + $user = $this->userProvider->findWhere($conditions); + + if ($user) { + $this->setUser($user); + } + + return $user; + } + + public function authenticateWithEmail($email) + { + $user = $this->userProvider->findByEmail($email); + + if (!$user) { + throw new UserWithEmailNotFoundException($email); + } + + $this->setUser($user); + + return $user; + } + + /** + * Authenticate an user using a private token + * + * @param $token + * + * @return UserInterface + * + * @throws InvalidTokenException + */ + public function authenticateWithPrivateToken($token) + { + $conditions = [ + 'token' => $token + ]; + + $user = $this->userProvider->findWhere($conditions); + if ($user) { + $this->setUser($user); + } + + return $user; + } + + /** + * Authenticate with an invitation + * + * NOTE: Would this be managed by the web app? + * + * @param $invitationCode + * + * @return UserInterface + * + * @throws InvalidInvitationCodeException + */ + public function authenticateWithInvitation($invitationCode) + { + $user = $this->userProvider->findWhere(['invite_token' => $invitationCode]); + + if ($user) { + $this->setUser($user); + } + + return $user; + } + + /** + * Force a user id to be the logged user + * + * TODO: Change this method name + * + * @param UserInterface $user The User account's ID. + * + * @return UserInterface + * + * @throws UserNotFoundException + */ + public function forceUserLogin(UserInterface $user) + { + $this->setUser($user); + + return $user; + } + + /** + * Check if a user is logged in. + * + * @return boolean + */ + public function check() + { + return $this->authenticated; + } + + /** + * Retrieve metadata about the currently logged in user. + * + * @param null|string $attribute + * + * @return mixed|array Authenticated user metadata. + * + * @throws \Directus\Authentication\Exception\UserNotAuthenticatedException + */ + public function getUserAttributes($attribute = null) + { + $this->enforceUserIsAuthenticated(); + $user = $this->user->toArray(); + + if ($attribute !== null) { + return array_key_exists($attribute, $user) ? $user[$attribute] : null; + } + + return $user; + } + + /** + * Gets authenticated user object + * + * @return UserInterface|null + */ + public function getUser() + { + return $this->user; + } + + /** + * Gets the user provider + * + * @return UserProviderInterface + */ + public function getUserProvider() + { + return $this->userProvider; + } + + /** + * Generates a new access token + * + * @param UserInterface $user + * + * @return string + */ + public function generateAuthToken(UserInterface $user) + { + $payload = [ + 'id' => (int) $user->getId(), + // 'group' => $user->getGroupId(), + 'exp' => $this->getNewExpirationTime() + ]; + + return $this->generateToken(JWTUtils::TYPE_AUTH, $payload); + } + + /** + * Generates a new request token used to SSO to request an Access Token + * + * @param UserInterface $user + * + * @return string + */ + public function generateRequestToken(UserInterface $user) + { + $payload = [ + 'type' => 'request_token', + 'id' => (int) $user->getId(), + // 'group' => (int) $user->getGroupId(), + 'exp' => time() + (5 * DateTimeUtils::MINUTE_IN_SECONDS), + 'url' => get_url(), + 'env' => get_api_env_from_request() + ]; + + return $this->generateToken(JWTUtils::TYPE_SSO_REQUEST_TOKEN, $payload); + } + + /** + * Generates a new reset password token + * + * @param UserInterface $user + * + * @return string + */ + public function generateResetPasswordToken(UserInterface $user) + { + $payload = [ + 'id' => (int) $user->getId(), + 'email' => $user->getEmail(), + // TODO: Separate time expiration for reset password token + 'exp' => $this->getNewExpirationTime() + ]; + + return $this->generateToken(JWTUtils::TYPE_RESET_PASSWORD, $payload); + } + + /** + * Generate invitation token + * + * @param array $payload + * + * @return string + */ + public function generateInvitationToken(array $payload) + { + return $this->generateToken(JWTUtils::TYPE_INVITATION, $payload); + } + + /** + * Generates a new JWT token + * + * @param string $type + * @param array $payload + * + * @return string + */ + public function generateToken($type, array $payload) + { + $payload['type'] = (string)$type; + + return JWTUtils::encode($payload, $this->getSecretKey(), 'HS256'); + } + + /** + * Refresh valid token expiration + * + * @param $token + * + * @return string + * + * @throws ExpiredTokenException + * @throws InvalidTokenException + */ + public function refreshToken($token) + { + $algo = 'HS256'; + $payload = JWTUtils::decode($token, $this->getSecretKey(), [$algo]); + if (!JWTUtils::hasPayloadType(JWTUtils::TYPE_AUTH, $payload)) { + throw new InvalidTokenException(); + } + + if (!is_object($payload)) { + // Empty payload, log this as debug? + throw new InvalidTokenException(); + } + + $payload->exp = $this->getNewExpirationTime(); + + return JWTUtils::encode($payload, $this->getSecretKey(), $algo); + } + + /** + * Run the hashing algorithm on a password and salt value. + * + * @param string $password + * + * @return string + */ + public function hashPassword($password) + { + // TODO: Create a library to hash/verify passwords up to the user which algorithm to use + return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]); + } + + /** + * Authentication secret key + * + * @return string + */ + public function getSecretKey() + { + return $this->secretKey; + } + + /** + * @return int + */ + public function getNewExpirationTime() + { + return time() + ($this->ttl * DateTimeUtils::MINUTE_IN_SECONDS); + } + + /** + * Sets the given user as authenticated + * + * @param UserInterface $user + * + * @throws UserInactiveException + * @throws UserNotFoundException + */ + protected function setUser(UserInterface $user) + { + if (!$user->getId()) { + throw new UserNotFoundException(); + } + + if (!$this->isActive($user)) { + throw new UserInactiveException(); + } + + $this->authenticated = true; + $this->user = $user; + } +} diff --git a/src/core/Directus/Authentication/Sso/AbstractSocialProvider.php b/src/core/Directus/Authentication/Sso/AbstractSocialProvider.php new file mode 100644 index 0000000000..eb8c2f6213 --- /dev/null +++ b/src/core/Directus/Authentication/Sso/AbstractSocialProvider.php @@ -0,0 +1,92 @@ +container = $container; + $this->config = new Collection($config); + + $this->createProvider(); + } + + /** + * Gets provider instance + * + * @return mixed + */ + public function getProvider() + { + if (!$this->provider) { + $this->createProvider(); + } + + return $this->provider; + } + + /** + * @inheritdoc + */ + public function getConfig() + { + return $this->config; + } + + /** + * Gets authorization token + * + * @return string|null + */ + public function getToken() + { + return $this->token; + } + + /** + * Gets the redirect url for the given service name + * + * @return string + */ + public function getRedirectUrl() + { + return $this->config->get('callback_url'); + } + + /** + * Creates the provider oAuth client + * + * @return mixed + */ + abstract protected function createProvider(); +} diff --git a/src/core/Directus/Authentication/Sso/OneSocialProvider.php b/src/core/Directus/Authentication/Sso/OneSocialProvider.php new file mode 100644 index 0000000000..44529ded87 --- /dev/null +++ b/src/core/Directus/Authentication/Sso/OneSocialProvider.php @@ -0,0 +1,88 @@ +getScopes(); + + if ($scopes) { + $options['scope'] = $this->getScopes(); + } + + return $this->provider->getAuthorizationUrl($options); + } + + /** + * @inheritDoc + */ + public function request() + { + // These identify you as a client to the server. + $temporaryCredentials = $this->provider->getTemporaryCredentials(); + + // Store the credentials in the session. + $session = $this->container->get('session'); + $session->set('oauth1cred', serialize($temporaryCredentials)); + + // resource owner to the login screen on the server. + $this->provider->authorize($temporaryCredentials); + + return; + } + + /** + * @inheritDoc + */ + public function handle() + { + return $this->getUserFromCode([ + 'oauth_token' => ArrayUtils::get($_GET, 'oauth_token'), + 'oauth_verifier' => ArrayUtils::get($_GET, 'oauth_verifier') + ]); + } + + /** + * @inheritdoc + */ + public function getUserFromCode(array $data) + { + $oauthToken = ArrayUtils::get($data, 'oauth_token'); + $oauthVerifier = ArrayUtils::get($data, 'oauth_verifier'); + + if (!isset($oauthToken) || !isset($oauthVerifier)) { + throw new \Exception('Invalid request'); + } + + $session = $this->container->get('session'); + + // Retrieve the temporary credentials from step 2 + $temporaryCredentials = unserialize($session->get('oauth1cred')); + + // Third and final part to OAuth 1.0 authentication is to retrieve token + // credentials (formally known as access tokens in earlier OAuth 1.0 + // specs). + $tokenCredentials = $this->provider->getTokenCredentials($temporaryCredentials, $oauthToken, $oauthVerifier); + + $user = $this->provider->getUserDetails($tokenCredentials); + + return new SocialUser([ + 'email' => $user->email + ]); + } + + /** + * Get the list of scopes for the current service + * + * @return array + */ + abstract public function getScopes(); +} diff --git a/src/core/Directus/Authentication/Sso/Social.php b/src/core/Directus/Authentication/Sso/Social.php new file mode 100644 index 0000000000..8001c60b99 --- /dev/null +++ b/src/core/Directus/Authentication/Sso/Social.php @@ -0,0 +1,83 @@ + $provider) { + $this->register($key, $provider); + } + } + + /** + * Register a provider + * + * @param string $key + * @param SocialProviderInterface $provider + * + * @return $this + */ + public function register($key, $provider) + { + if (!$key || !is_string($key)) { + throw new \RuntimeException('Social Login name must be a string'); + } + + if (ArrayUtils::has($this->providers, $key)) { + throw new \RuntimeException(sprintf('Social Login "%s" already exists', $key)); + } + + if (!($provider instanceof SocialProviderInterface)) { + throw new RuntimeException( + sprintf( + 'Single Sign On provider must be a instance of %s, instead %s was given', + SocialProviderInterface::class, gettype($provider) + ) + ); + } + + $this->providers[$key] = $provider; + + return $this; + } + + /** + * Gets a provider by its key + * + * @param $key + * + * @throws \Exception + * + * @return mixed|null + */ + public function get($key) + { + if (!array_key_exists($key, $this->providers)) { + throw new \Exception(sprintf('auth provider "%s" does not exist.', $key)); + } + + return $this->providers[$key]; + } + + /** + * Get all registered providers + * + * @return SocialProviderInterface[] + */ + public function getAll() + { + return $this->providers; + } +} diff --git a/src/core/Directus/Authentication/Sso/SocialProviderInterface.php b/src/core/Directus/Authentication/Sso/SocialProviderInterface.php new file mode 100644 index 0000000000..f5f319bd62 --- /dev/null +++ b/src/core/Directus/Authentication/Sso/SocialProviderInterface.php @@ -0,0 +1,46 @@ +get('social_token'); + } +} diff --git a/src/core/Directus/Authentication/Sso/TwoSocialProvider.php b/src/core/Directus/Authentication/Sso/TwoSocialProvider.php new file mode 100644 index 0000000000..91d6d7eb5e --- /dev/null +++ b/src/core/Directus/Authentication/Sso/TwoSocialProvider.php @@ -0,0 +1,77 @@ + $this->getScopes() + ]; + + return $this->provider->getAuthorizationUrl($options); + } + + /** + * @inheritDoc + */ + public function request() + { + $requestUrl = $this->getRequestAuthorizationUrl(); + + header('Location: ' . $requestUrl); + } + + /** + * @inheritdoc + */ + public function handle() + { + return $this->getUserFromCode([ + 'code' => ArrayUtils::get($_GET, 'code') + ]); + } + + /** + * @inheritdoc + */ + public function getUserFromCode(array $data) + { + // Try to get an access token (using the authorization code grant) + $token = $this->provider->getAccessToken('authorization_code', [ + 'code' => ArrayUtils::get($data, 'code') + ]); + + return new SocialUser([ + 'email' => $this->getResourceOwnerEmail($token) + ]); + } + + /** + * Gets the resource owner email + * + * @param AccessToken $token + * + * @return string + */ + protected function getResourceOwnerEmail(AccessToken $token) + { + $user = $this->provider->getResourceOwner($token); + + return $user->getEmail(); + } + + /** + * Get the list of scopes for the current service + * + * @return array + */ + abstract public function getScopes(); +} diff --git a/src/core/Directus/Authentication/User/Provider/UserProviderInterface.php b/src/core/Directus/Authentication/User/Provider/UserProviderInterface.php new file mode 100644 index 0000000000..7f96c152ca --- /dev/null +++ b/src/core/Directus/Authentication/User/Provider/UserProviderInterface.php @@ -0,0 +1,53 @@ +tableGateway = $tableGateway; + } + + /** + * @inheritdoc + */ + public function findWhere(array $conditions) + { + $user = null; + + $select = new Select($this->tableGateway->getTable()); + $select->where($conditions); + $select->limit(1); + + /** @var BaseRowGateway $row */ + $row = $this->tableGateway + ->ignoreFilters() + ->selectWith($select) + ->current(); + + if ($row) { + $user = new User($row->toArray()); + } + + return $user; + } + + /** + * @inheritdoc + */ + public function find($id) + { + return $this->findWhere(['id' => $id]); + } + + /** + * @inheritdoc + */ + public function findByEmail($email) + { + return $this->findWhere(['email' => $email]); + } + + /** + * @inheritdoc + */ + public function update(UserInterface $user, array $data) + { + $condition = [ + $this->tableGateway->primaryKeyFieldName => $user->id + ]; + + return (bool)$this->tableGateway->ignoreFilters()->update($data, $condition); + } +} diff --git a/src/core/Directus/Authentication/User/User.php b/src/core/Directus/Authentication/User/User.php new file mode 100644 index 0000000000..ca0368972e --- /dev/null +++ b/src/core/Directus/Authentication/User/User.php @@ -0,0 +1,84 @@ +attributes = $attributes; + } + + /** + * Gets the attribute with the given name + * + * @param $name + * + * @return mixed + */ + public function get($name) + { + return ArrayUtils::get($this->attributes, $name); + } + + /** + * @inheritdoc + */ + public function getId() + { + return $this->get('id'); + } + + /** + * @inheritdoc + */ + public function getEmail() + { + return $this->get('email'); + } + + /** + * @inheritdoc + */ + public function getGroupId() + { + return $this->get('group'); + } + + /** + * Access the attribute as property + * + * @param $name + * + * @return mixed + * + * @throws UnknownUserAttributeException + */ + public function __get($name) + { + if (!array_key_exists($name, $this->attributes)) { + throw new UnknownUserAttributeException(sprintf('Property "%s" does not exists.', $name)); + } + + // TODO: Omit sensitive data + return $this->attributes[$name]; + } + + /** + * @inheritdoc + */ + public function toArray() + { + return $this->attributes; + } +} diff --git a/src/core/Directus/Authentication/User/UserInterface.php b/src/core/Directus/Authentication/User/UserInterface.php new file mode 100644 index 0000000000..500a14d6ba --- /dev/null +++ b/src/core/Directus/Authentication/User/UserInterface.php @@ -0,0 +1,44 @@ +pool = $pool; + $this->defaultTtl = $defaultTtl; + + } + + public function getPool() + { + return $this->pool; + } + + /** + * @param $key + * @param bool $value + * @param array $tags + * @param int $ttl + * @return TaggableCacheItemInterface + */ + public function set($key, $value = null, $tags = [], $ttl = null) + { + $ttl = ($ttl) ? $ttl : $this->defaultTtl; + + $item = $this->getPool()->getItem($key)->set($value); + + if($tags) { + $item->setTags($tags); + } + + if($ttl) { + $item->expiresAfter($ttl); + } + + $this->getPool()->save($item); + + return $item; + } + + public function get($key) + { + return $this->getPool()->getItem($key)->get(); + } +} \ No newline at end of file diff --git a/src/core/Directus/Cache/Response.php b/src/core/Directus/Cache/Response.php new file mode 100644 index 0000000000..cd4bb89ec2 --- /dev/null +++ b/src/core/Directus/Cache/Response.php @@ -0,0 +1,31 @@ +tags = array_merge($this->tags, (array)$tags); + + return $this; + } + + public function ttl($time) + { + $this->ttl = $time; + } + + public function process($key, $bodyContent, $headers = []) + { + if ($key && !empty($this->tags)) { + $value = ['body' => $bodyContent, 'headers' => $headers]; + + return $this->set($key, $value, $this->tags, $this->defaultTtl); + } + + return false; + } +} diff --git a/src/core/Directus/Collection/Arrayable.php b/src/core/Directus/Collection/Arrayable.php new file mode 100644 index 0000000000..8c9822f9d8 --- /dev/null +++ b/src/core/Directus/Collection/Arrayable.php @@ -0,0 +1,13 @@ +items = $items; + } + + /** + * @inheritDoc + */ + public function toArray() + { + return $this->items; + } + + /** + * @inheritDoc + */ + public function set($key, $value) + { + $this->items[$key] = $value; + } + + /** + * @inheritDoc + */ + public function get($key, $default = null) + { + return ArrayUtils::get($this->items, $key, $default); + } + + /** + * @inheritDoc + */ + public function has($key) + { + return ArrayUtils::has($this->items, $key); + } + + /** + * @inheritDoc + */ + public function remove($key) + { + if ($this->has($key)) { + unset($this->items[$key]); + } + } + + /** + * @inheritDoc + */ + public function isEmpty() + { + return $this->count() === 0; + } + + /** + * @inheritDoc + */ + public function clear() + { + $this->items = []; + } + + /** + * @inheritDoc + */ + public function replace(array $items) + { + $this->clear(); + $this->appendArray($items); + } + + /** + * @inheritDoc + */ + public function appendArray(array $items) + { + $this->items = array_merge($this->items, $items); + + return $this->items; + } + + /** + * @inheritDoc + */ + public function appendCollection(Collection $collection) + { + return $this->appendArray($collection->toArray()); + } + + /** + * @inheritDoc + */ + public function offsetExists($offset) + { + return $this->has($offset); + } + + /** + * @inheritDoc + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * @inheritDoc + */ + public function count() + { + return count($this->items); + } + + /** + * @inheritDoc + */ + public function current() + { + return current($this->items); + } + + /** + * @inheritDoc + */ + public function next() + { + return next($this->items); + } + + /** + * @inheritDoc + */ + public function key() + { + return key($this->items); + } + + /** + * @inheritDoc + */ + public function valid() + { + return $this->key() !== null; + } + + /** + * @inheritDoc + */ + public function rewind() + { + return reset($this->items); + } +} diff --git a/src/core/Directus/Collection/CollectionInterface.php b/src/core/Directus/Collection/CollectionInterface.php new file mode 100644 index 0000000000..cd3b4635cc --- /dev/null +++ b/src/core/Directus/Collection/CollectionInterface.php @@ -0,0 +1,85 @@ +=5.4.0" + }, + "autoload": { + "psr-4": { + "Directus\\Collection\\": "" + } + } +} diff --git a/src/core/Directus/Config/Config.php b/src/core/Directus/Config/Config.php new file mode 100644 index 0000000000..b783b693c5 --- /dev/null +++ b/src/core/Directus/Config/Config.php @@ -0,0 +1,10 @@ + '#000000', + 'background_color' => '#FFFFFF', + 'listing_subdued' => false, + 'listing_badge' => false, + 'soft_delete' => false, + 'hard_delete' => false, + 'published' => true, + ]; + + public function __construct($value, $name, $sort, array $attributes = []) + { + $this->value = $value; + $this->name = $name; + $this->sort = $sort; + $this->attributes = array_merge($this->defaultAttributes, $attributes); + } + + /** + * @return int + */ + public function getValue() + { + return $this->value; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return int + */ + public function getSort() + { + return $this->sort; + } + + /** + * @return bool + */ + public function isSoftDelete() + { + return $this->getAttribute('soft_delete') == true; + } + + /** + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * @param $key + * + * @return mixed + */ + public function getAttribute($key) + { + return ArrayUtils::get($this->attributes, $key); + } +} diff --git a/src/core/Directus/Config/StatusMapping.php b/src/core/Directus/Config/StatusMapping.php new file mode 100644 index 0000000000..b8108fb66e --- /dev/null +++ b/src/core/Directus/Config/StatusMapping.php @@ -0,0 +1,120 @@ + $status) { + if (!is_array($status) || !ArrayUtils::contains($status, 'name')) { + throw new InvalidStatusException('status must be an array with a name attribute'); + } + + $name = ArrayUtils::pull($status, 'name'); + $sort = ArrayUtils::pull($status, 'sort'); + + $items[] = new StatusItem($value, $name, $sort, $status); + } + + parent::__construct($items); + } + + public function add(StatusItem $status) + { + $this->items[] = $status; + } + + /** + * Get status by value + * + * @param mixed $value + * + * @return StatusItem + */ + public function getByValue($value) + { + foreach ($this->items as $item) { + if ($item->getValue() == $value) { + return $item; + } + } + } + + /** + * Get a list of published statuses + * + * @return array + */ + public function getPublishedStatusesValue() + { + return $this->getStatusesValue('published', true); + } + + /** + * Returns a list of soft delete status values + * + * @return array + */ + public function getSoftDeleteStatusesValue() + { + return $this->getStatusesValue('soft_delete', true); + } + + /** + * Returns a list of non-soft-delete status values + * + * @return array + */ + public function getNonSoftDeleteStatusesValue() + { + return $this->getStatusesValue('soft_delete', false); + } + + /** + * Get all statuses value + * + * @return array + */ + public function getAllStatusesValue() + { + $statuses = []; + + foreach ($this->items as $status) { + $statuses[] = $status->getValue(); + } + + return $statuses; + } + + /** + * Get statuses list by the given type + * + * @param string $type + * @param mixed $value + * + * @return array + */ + protected function getStatusesValue($type, $value) + { + $statuses = []; + foreach ($this->items as $status) { + if ($status->getAttribute($type) === $value) { + $statuses[] = $status->getValue(); + } + } + + return $statuses; + } +} diff --git a/src/core/Directus/Config/composer.json b/src/core/Directus/Config/composer.json new file mode 100644 index 0000000000..fb6af932bf --- /dev/null +++ b/src/core/Directus/Config/composer.json @@ -0,0 +1,24 @@ +{ + "name": "directus/config", + "description": "Directus Config Library", + "keywords": [ + "directus", + "config" + ], + "license": "MIT", + "require": { + "php": ">=5.4.0", + "directus/collection": "^1.0.0", + "directus/utils": "^1.0.0" + }, + "autoload": { + "psr-4": { + "Directus\\Config\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-version/6.4": "0.9.x-dev" + } + } +} diff --git a/src/core/Directus/Console/Cli.php b/src/core/Directus/Console/Cli.php new file mode 100644 index 0000000000..aa1a70c743 --- /dev/null +++ b/src/core/Directus/Console/Cli.php @@ -0,0 +1,164 @@ +directusPath = $directusPath; + + $this->command = array_shift($argv); + + if ($this->command == 'help') { + $this->help_module = array_shift($argv); + } else { + $this->options = $this->parseOptions($argv); + } + + $this->loadModules(); + } + + private function loadModules() + { + foreach (glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Modules' . DIRECTORY_SEPARATOR . '*Module.php') as $moduleFile) { + $moduleName = 'Directus\Console\Modules\\' . basename($moduleFile, '.php'); + require($moduleFile); + $moduleInstance = new $moduleName($this->directusPath); + $this->cmd_modules[$moduleInstance->getModuleName()] = $moduleInstance; + } + } + + public function run() + { + switch ($this->command) { + case 'help': + case '': + if ($this->help_module != '') { + $this->showModuleHelp(); + } else { + $this->showHelp(); + } + break; + default: + $this->cmd(); + break; + } + } + + private function cmd() + { + list($module, $command) = explode(':', $this->command . ':'); + + if (empty($command)) { + echo PHP_EOL . 'Error: Missing module name or command.' . PHP_EOL . PHP_EOL; + echo 'Command are executed as follows:' . PHP_EOL; + echo "\tdirectus : " . PHP_EOL . PHP_EOL; + echo "Example: " . PHP_EOL . "\tdirectus install:help database" . PHP_EOL . PHP_EOL; + return; + } + + if (!array_key_exists($module, $this->cmd_modules)) { + echo PHP_EOL . PHP_EOL . 'Module ' . $module . ': does not exists!' . PHP_EOL . PHP_EOL; + return; + } + + try { + $this->cmd_modules[$module]->runCommand($command, $this->options, $this->extra); + } catch (WrongArgumentsException $e) { + echo PHP_EOL . PHP_EOL . 'Module ' . $module . ' error: ' . $e->getMessage() . PHP_EOL . PHP_EOL; + } catch (UnsupportedCommandException $e) { + echo PHP_EOL . PHP_EOL . 'Module ' . $module . ' error: ' . $e->getMessage() . PHP_EOL . PHP_EOL; + } + } + + private function showHelp() + { + echo PHP_EOL . 'Directus CLI Modules: ' . PHP_EOL . PHP_EOL; + foreach ($this->cmd_modules as $name => $module) { + echo "\t" . $module->getInfo() . PHP_EOL; + } + echo PHP_EOL . 'For more information on a module use: "directus help "' . PHP_EOL . PHP_EOL; + } + + private function showModuleHelp() + { + if (!array_key_exists($this->help_module, $this->cmd_modules)) { + echo PHP_EOL . PHP_EOL . 'Module ' . $this->help_module . ': does not exists!' . PHP_EOL . PHP_EOL; + return; + } + echo PHP_EOL . 'Directus Module ' . ucfirst($this->help_module) . ' Commands' . PHP_EOL . PHP_EOL; + $module_commands = $this->cmd_modules[$this->help_module]->getCommands(); + foreach ($module_commands as $command => $cmd_help) { + echo "\t" . $cmd_help . PHP_EOL . PHP_EOL; + } + echo PHP_EOL . 'For more information on a command use: "directus :help command"' . PHP_EOL . PHP_EOL; + } + + private function parseOptions($argv) + { + $options = []; + + $num_args = count($argv); + $arg_idx = 0; + while ($num_args > 0) { + $arg = $argv[$arg_idx]; + if (preg_match("/^(-{1,2})([A-Za-z0-9-_]+)$/", $arg, $argMatch)) { + $key = $argMatch[2]; + /** + * There is still at least an argument - this could be the + * value for this option. + */ + if ($num_args >= 2) { + if (preg_match("/^(-{1,2})([A-Za-z0-9-_]+)$/", $argv[$arg_idx + 1], $nextMatch)) { + /** + * Next argument is another option - so this must be a switch. + */ + $value = true; + $arg_idx++; + $num_args--; + } else { + /** + * The next argument is the value for this option. + */ + $value = $argv[$arg_idx + 1]; + $arg_idx += 2; + $num_args -= 2; + } + } else { + /** + * There is no other value, this is just a switch. + */ + $value = true; + $arg_idx++; + $num_args--; + } + $options[$key] = $value; + } else { + $this->extra[] = $arg; + $arg_idx++; + $num_args--; + } + } + + return $options; + } +} diff --git a/src/core/Directus/Console/Common/Exception/PasswordChangeException.php b/src/core/Directus/Console/Common/Exception/PasswordChangeException.php new file mode 100644 index 0000000000..1759f83afc --- /dev/null +++ b/src/core/Directus/Console/Common/Exception/PasswordChangeException.php @@ -0,0 +1,11 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/core/Directus/Console/Common/Exception/SettingUpdateException.php b/src/core/Directus/Console/Common/Exception/SettingUpdateException.php new file mode 100644 index 0000000000..c20554d25b --- /dev/null +++ b/src/core/Directus/Console/Common/Exception/SettingUpdateException.php @@ -0,0 +1,12 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/core/Directus/Console/Common/Exception/UserUpdateException.php b/src/core/Directus/Console/Common/Exception/UserUpdateException.php new file mode 100644 index 0000000000..fe1419d231 --- /dev/null +++ b/src/core/Directus/Console/Common/Exception/UserUpdateException.php @@ -0,0 +1,11 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/core/Directus/Console/Common/Setting.php b/src/core/Directus/Console/Common/Setting.php new file mode 100644 index 0000000000..4f6f79b48c --- /dev/null +++ b/src/core/Directus/Console/Common/Setting.php @@ -0,0 +1,147 @@ +directus_path = $base_path; + $app = new Application($base_path, require $this->directus_path . '/config/api.php'); + $this->db = $app->getContainer()->get('database'); + + $this->settingsTableGateway = new TableGateway('directus_settings', $this->db); + } + + /** + * Check if base settings have already been defined. + * + * The function return true if base settings have already been defined, + * false in any othe case. + * + * @return boolean True if settings have already been defined. False in any + * other case. + * + */ + public function isConfigured() + { + try { + $rowset = $this->settingsTableGateway->select(); + if ($rowset->count() > 0) { + return true; + } + return false; + } catch (PDOException $ex) { + return false; + } + } + + /** + * Check if a settings has already been defined. + * + * The function return true if the setting has already been defined for a collection, + * false in any othe case. + * + * @param string $collection The collection to which this setting applies. + * @param string $setting The name of the setting to check. + * + * @return boolean True if setting has been defined for the collection. False in any + * other case. + * + */ + public function settingExists($collection, $setting) + { + try { + $rowset = $this->settingsTableGateway->select([ + 'scope' => $collection, + 'key' => $setting + ]); + if ($rowset->count() > 0) { + return true; + } + return false; + } catch (PDOException $ex) { + return false; + } + } + + /** + * Creates a setting and sets its value for Directus. + * + * The function will create the given setting with the passed value. + * + * @param string $collection The collection to which this setting applies. + * @param string $setting The name of the setting to create. + * @param string $value The value of the setting. + * + * @return void + * + * @throws SettingUpdateException Thrown when the creation of the setting fails. + * + */ + public function createSetting($collection, $setting, $value) + { + + $insert = [ + 'scope' => $collection, + 'key' => $setting, + 'value' => $value + ]; + + try { + $this->settingsTableGateway->insert($insert); + } catch (\PDOException $ex) { + throw new SettingUpdateException('Could not create setting ' . $collection . '.' . $setting . ': ' . 'PDO Error: ' . string($ex)); + } + + } + + /** + * Sets the value of a setting for Directus, creating it if needed. + * + * The function will change the given setting to the passed value if it already + * exists and will create it if it doesn't. + * + * @param string $scope The collection to which this setting applies. + * @param string $setting The name of the setting to change. + * @param string $value The value of the setting. + * + * @return void + * + * @throws SettingUpdateException Thrown when the changing the setting fails. + * + */ + public function setSetting($scope, $setting, $value) + { + + if (!$this->settingExists($scope, $setting)) { + return $this->createSetting($scope, $setting, $value); + } + + $update = [ + 'value' => $value + ]; + + try { + $this->settingsTableGateway->update($update, [ + 'scope' => $scope, + 'key' => $setting + ]); + } catch (\PDOException $ex) { + throw new SettingUpdateException('Could not change setting ' . $scope . '.' . $setting . ': ' . 'PDO Error: ' . string($ex)); + } + } + +} diff --git a/src/core/Directus/Console/Common/User.php b/src/core/Directus/Console/Common/User.php new file mode 100644 index 0000000000..fbaf1b07a5 --- /dev/null +++ b/src/core/Directus/Console/Common/User.php @@ -0,0 +1,107 @@ +directus_path = $base_path; + $this->app = new Application($base_path, require $base_path. '/config/api.php'); + $this->db = $this->app->getContainer()->get('database'); + + $this->usersTableGateway = new TableGateway('directus_users', $this->db); + } + + /** + * Change the password of a user given their e-mail address + * + * The function will change the password of a user given their e-mail + * address. If there are multiple users with the same e-mail address, and + * this should never be the case, all of their passwords would be changed. + * + * The function will generate a new salt for every password change. + * + * @param string $email The e-mail of the user whose password is being + * changed. + * @param string $password The new password. + * + * @return void + * + * @throws PasswordChangeException Thrown when password change has failed. + * + */ + public function changePassword($email, $password) + { + + $auth = $this->app->getContainer()->get('auth'); + $hash = $auth->hashPassword($password); + $user = $this->usersTableGateway->select(['email' => $email])->current(); + + if (!$user) { + throw new \InvalidArgumentException('User not found'); + } + + try { + $update = [ + 'password' => $hash + ]; + + $changed = $this->usersTableGateway->update($update, ['email' => $email]); + if ($changed == 0) { + throw new PasswordChangeException('Could not change password for ' . $email . ': ' . 'e-mail not found.'); + } + } catch (\PDOException $ex) { + throw new PasswordChangeException('Failed to change password' . ': ' . str($ex)); + } + + } + + /** + * Change the e-mail of a user given their ID. + * + * The function will change the e-mail of a user given their ID. This may have + * undesired effects, this function is mainly useful during the setup/install + * phase. + * + * + * @param string $id The ID of the user whose e-mail address we want to change. + * @param string $email The new e-mail address for the use. + * + * @return void + * + * @throws UserUpdateException Thrown when the e-mail address change fails. + * + */ + public function changeEmail($id, $email) + { + + $update = [ + 'email' => $email + ]; + + try { + $this->usersTableGateway->update($update, ['id' => $id]); + } catch (\PDOException $ex) { + throw new PasswordChangeException('Could not change email for ID ' . $id . ': ' . str($ex)); + } + } + +} diff --git a/src/core/Directus/Console/Exception/CommandFailedException.php b/src/core/Directus/Console/Exception/CommandFailedException.php new file mode 100644 index 0000000000..ecca00797c --- /dev/null +++ b/src/core/Directus/Console/Exception/CommandFailedException.php @@ -0,0 +1,11 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/core/Directus/Console/Exception/UnsupportedCommandException.php b/src/core/Directus/Console/Exception/UnsupportedCommandException.php new file mode 100644 index 0000000000..1a4fa2a620 --- /dev/null +++ b/src/core/Directus/Console/Exception/UnsupportedCommandException.php @@ -0,0 +1,12 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/core/Directus/Console/Exception/WrongArgumentsException.php b/src/core/Directus/Console/Exception/WrongArgumentsException.php new file mode 100644 index 0000000000..bf530803fb --- /dev/null +++ b/src/core/Directus/Console/Exception/WrongArgumentsException.php @@ -0,0 +1,11 @@ +code}]: {$this->message}\n"; + } +} diff --git a/src/core/Directus/Console/Modules/CacheModule.php b/src/core/Directus/Console/Modules/CacheModule.php new file mode 100644 index 0000000000..7ec915c489 --- /dev/null +++ b/src/core/Directus/Console/Modules/CacheModule.php @@ -0,0 +1,85 @@ +commands_help = [ + 'clear' => 'Clear the whole cache', + 'delete' => 'Delete cache item', + 'get' => 'Get item from cache', + 'invalidate' => 'Invalidate specified tags', + ]; + + $this->help = [ + 'clear' => '-f ' . 'force (do nothing otherwise)', + 'delete' => 'Item name', + 'get' => 'Item name'.'. -e '.'to var_export the value (should be used after the value key', + 'invalidate' => 'List of tags to invalidate' + ]; + + $helpArrays = [ + 'commands_help' => $this->__module_name.':%s – %s', + 'help' => "\t\t".'%2$s' + ]; + + foreach($helpArrays as $arrayName => $format) + { + foreach($this->$arrayName as $key => &$value) { + $this->{$arrayName}[$key] = sprintf($format, $key, $value); + } + } + + // NOTE: Stripped just to make the tests works + // TODO: Bring it back + // $this->pool = Bootstrap::get('cache'); + } + + public function cmdInvalidate($args, $extra) + { + $this->pool->invalidateTags($extra); + } + + public function cmdGet($args, $extra) + { + if(!empty($extra[0]) && $key = $extra[0]) { + $item = $this->pool->getItem($key)->get(); + if(empty($args['e'])) { + echo $item; + } else { + var_export($item); + } + } + } + + public function cmdDelete($args, $extra) + { + if(isset($extra[0]) && $key = $extra[0]) { + $this->pool->delete($key); + } + } + + public function cmdClear($args, $extra) + { + if(empty($args['f'])) { + throw new WrongArgumentsException($this->__module_name . ':clear ' . 'Refusing to do anything without -f'); + } + + $this->pool->clear(); + } + +} diff --git a/src/core/Directus/Console/Modules/DatabaseModule.php b/src/core/Directus/Console/Modules/DatabaseModule.php new file mode 100644 index 0000000000..147994d045 --- /dev/null +++ b/src/core/Directus/Console/Modules/DatabaseModule.php @@ -0,0 +1,70 @@ + 'Install the database schema', + 'upgrade' => 'Upgrade the database schema' + ]; + $this->help = $this->commands_help = $commands; + } + + public function cmdHelp($args, $extra) + { + echo PHP_EOL . 'Database Command ' . $this->__module_name . ':' . $extra[0] . ' help' . PHP_EOL . PHP_EOL; + echo "\t" . $this->commands_help[$extra[0]] . PHP_EOL; + echo PHP_EOL . PHP_EOL; + } + + public function cmdInstall($args, $extra) + { + $this->runMigration('schema'); + } + + public function cmdUpgrade($args, $extra) + { + $this->runMigration('upgrades'); + } + + protected function runMigration($name) + { + $directusPath = $this->getBasePath(); + + $configPath = $directusPath . '/config'; + $apiConfig = require $configPath . '/api.php'; + $configArray = require $configPath . '/migrations.php'; + $configArray['paths']['migrations'] = $directusPath . '/migrations/db/' . $name; + $configArray['environments']['development'] = [ + 'adapter' => ArrayUtils::get($apiConfig, 'database.type'), + 'host' => ArrayUtils::get($apiConfig, 'database.host'), + 'port' => ArrayUtils::get($apiConfig, 'database.port'), + 'name' => ArrayUtils::get($apiConfig, 'database.name'), + 'user' => ArrayUtils::get($apiConfig, 'database.username'), + 'pass' => ArrayUtils::get($apiConfig, 'database.password'), + 'charset' => ArrayUtils::get($apiConfig, 'database.charset', 'utf8') + ]; + $config = new Config($configArray); + + $manager = new Manager($config, new StringInput(''), new NullOutput()); + $manager->migrate('development'); + + // TODO: Flush Output + } +} diff --git a/src/core/Directus/Console/Modules/InstallModule.php b/src/core/Directus/Console/Modules/InstallModule.php new file mode 100644 index 0000000000..197bcc43ce --- /dev/null +++ b/src/core/Directus/Console/Modules/InstallModule.php @@ -0,0 +1,186 @@ +help = [ + 'config' => '' + . PHP_EOL . "\t\t-h " . 'Hostname or IP address of the MySQL DB to be used. Default: localhost' + . PHP_EOL . "\t\t-n " . 'Name of the database to use for Directus. Default: directus' + . PHP_EOL . "\t\t-u " . 'Username for DB connection. Default: directus' + . PHP_EOL . "\t\t-p " . 'Password for the DB connection user. Default: directus' + . PHP_EOL . "\t\t-t " . 'Database Server Type. Default: mysql' + . PHP_EOL . "\t\t-P " . 'Database Server Port. Default: 3306' + . PHP_EOL . "\t\t-c " . 'CORS Enabled. Default: false' + . PHP_EOL . "\t\t-r " . 'Directus root URI. Default: /', + 'database' => '', + 'migrate' => '', + 'install' => '' + . PHP_EOL . "\t\t-e " . 'Administrator e-mail address, used for administration login. Default: admin@directus.com' + . PHP_EOL . "\t\t-p " . 'Initial administrator password. Default: directus' + . PHP_EOL . "\t\t-t " . 'Name for this Directus installation. Default: Directus' + . PHP_EOL . "\t\t-T " . 'Administrator secret token. Default: Random' + . PHP_EOL . "\t\t-d " . 'Installation path of Directus. Default: ' . $this->getBasePath() + ]; + + $this->commands_help = [ + 'config' => 'Configure Directus: ' . PHP_EOL . PHP_EOL . "\t\t" + . $this->__module_name . ':config -h db_host -n db_name -u db_user -p db_pass -d directus_path' . PHP_EOL, + 'database' => 'Populate the Database Schema: ' . PHP_EOL . PHP_EOL . "\t\t" + . $this->__module_name . ':database -d directus_path' . PHP_EOL, + 'install' => 'Install Initial Configurations: ' . PHP_EOL . PHP_EOL . "\t\t" + . $this->__module_name . ':install -e admin_email -p admin_password -t site_name' . PHP_EOL, + ]; + } + + public function cmdConfig($args, $extra) + { + $data = []; + + $data['db_type'] = 'mysql'; + $data['db_port'] = '3306'; + $data['db_host'] = 'localhost'; + $data['db_name'] = ''; + $data['db_user'] = ''; + $data['db_password'] = ''; + + $directusPath = $this->getBasePath(); + + foreach ($args as $key => $value) { + switch ($key) { + case 't': + $data['db_type'] = $value; + break; + case 'P': + $data['db_port'] = $value; + break; + case 'h': + $data['db_host'] = $value; + break; + case 'n': + $data['db_name'] = $value; + break; + case 'u': + $data['db_user'] = $value; + break; + case 'p': + $data['db_password'] = $value; + break; + case 'r': + $directusPath = $value; + break; + case 'e': + $data['directus_email'] = $value; + break; + case 'c': + $data['cors_enabled'] = boolval($value); + break; + } + } + + $apiPath = rtrim($directusPath, '/') . '/config'; + if (!file_exists($apiPath)) { + throw new \Exception(sprintf('Path "%s" does not exists', $apiPath)); + } + + InstallerUtils::createConfig($data, $apiPath); + } + + public function cmdDatabase($args, $extra) + { + $directus_path = $this->getBasePath() . DIRECTORY_SEPARATOR; + foreach ($args as $key => $value) { + switch ($key) { + case 'd': + $directus_path = $value; + break; + } + } + + InstallerUtils::createTables($directus_path); + } + + public function cmdMigrate($args, $extra) + { + $directus_path = $this->getBasePath() . DIRECTORY_SEPARATOR; + + InstallerUtils::runMigration($directus_path); + } + + public function cmdSeeder($args, $extra) + { + $directus_path = $this->getBasePath() . DIRECTORY_SEPARATOR; + + InstallerUtils::runSeeder($directus_path); + } + + public function cmdInstall($args, $extra) + { + $data = []; + + $directus_path = $this->getBasePath() . DIRECTORY_SEPARATOR; + + foreach ($args as $key => $value) { + switch ($key) { + case 'e': + $data['directus_email'] = $value; + break; + case 'p': + $data['directus_password'] = $value; + break; + case 't': + $data['directus_name'] = $value; + break; + case 'T': + $data['directus_token'] = $value; + break; + case 'd': + $directus_path = $value; + break; + } + } + + try { + $setting = new Setting($directus_path); + + if (!$setting->isConfigured()) { + InstallerUtils::addDefaultSettings($data, $directus_path); + InstallerUtils::addDefaultUser($data, $directus_path); + } else { + $setting->setSetting('global', 'project_name', $data['directus_name']); + // NOTE: Do we really want to change the email when re-run install command? + $user = new User($directus_path); + try { + $user->changeEmail(1, $data['directus_email']); + $user->changePassword($data['directus_email'], $data['directus_password']); + } catch (UserUpdateException $ex) { + throw new CommandFailedException('Error changing admin e-mail' . ': ' . $ex->getMessage()); + } catch (PasswordChangeException $ex) { + throw new CommandFailedException('Error changing user password' . ': ' . $ex->getMessage()); + } + } + } catch (\PDOException $e) { + echo PHP_EOL . "PDO Excetion!!" . PHP_EOL; + echo PHP_EOL . PHP_EOL . 'Module ' . $this->__module_name . ' error: ' . $e->getMessage() . PHP_EOL . PHP_EOL; + } + } +} diff --git a/src/core/Directus/Console/Modules/ModuleBase.php b/src/core/Directus/Console/Modules/ModuleBase.php new file mode 100644 index 0000000000..3e70e03d8d --- /dev/null +++ b/src/core/Directus/Console/Modules/ModuleBase.php @@ -0,0 +1,75 @@ +basePath = $basePath; + } + + /** + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + public function getModuleName() + { + return $this->__module_name; + } + + public function getInfo() + { + return $this->__module_name . ': ' . $this->__module_description; + } + + public function getCommands() + { + return $this->commands_help; + } + + public function getCommandHelp($command) + { + if (!array_key_exists($command, $this->help)) { + throw new UnsupportedCommandException($this->__module_name . ':' . $command . ' command does not exists!'); + } + return $this->help[$command]; + } + + public function runCommand($command, $args, $extra) + { + $cmd_name = 'cmd' . ucwords($command); + if (!method_exists($this, $cmd_name)) { + throw new UnsupportedCommandException($this->__module_name . ':' . $command . ' command does not exists!'); + } + $this->$cmd_name($args, $extra); + } + + public function cmdHelp($args, $extra) + { + if (count($extra) == 0) { + throw new WrongArgumentsException($this->__module_name . ':help ' . 'missing command to show help for!'); + } + + echo PHP_EOL . 'Directus Command ' . $this->__module_name . ':' . $extra[0] . ' help' . PHP_EOL . PHP_EOL; + echo "\t" . $this->commands_help[$extra[0]] . PHP_EOL; + if(trim($this->getCommandHelp($extra[0]))) { + echo "\t" . 'Options: ' . PHP_EOL . $this->getCommandHelp($extra[0]); + } + echo PHP_EOL . PHP_EOL; + } +} diff --git a/src/core/Directus/Console/Modules/ModuleInterface.php b/src/core/Directus/Console/Modules/ModuleInterface.php new file mode 100644 index 0000000000..3db40298fe --- /dev/null +++ b/src/core/Directus/Console/Modules/ModuleInterface.php @@ -0,0 +1,90 @@ + + * $commands = array( + * 'config' => 'Configure Directus.', + * 'database' => 'Populate the DB with the schema.', + * 'install' => 'Install the initial configuration.' + * ); + * + * + * @return array[string]string A brief description of the module commands. + * + */ + public function getCommands(); + + /** + * Returns help for a command provided by this module. + * + * Provides help text for a specific command implemented by this module. + * + * @param string $command The command to get help for. + * + * @return string A brief description of the module command. + * + * @throws UnsupportedCommand Thrown when a module does not support a command. + */ + public function getCommandHelp($command); + + /** + * Executed a command provided by this module + * + * Executes a command provided by this module with the arguments passed to the + * function. + * + * Arguments are passed already parsed to the command in an associative array + * where the key is the name of the argument: + * + * + * $args = array( + * 'db_name' => 'directus', + * 'db_user' => 'directus_user' + * ); + * + * + * Extra - unamed - arguments are passed in a simple array of strings. + * + * @param string $command The command to execute. + * @param array [string]string $args The arguments for the command. + * @param array [string] $extra Un-named arguments passed to the command. + * + * @return void This function does not return a value. + * + * @throws UnsupportedCommand if the module does not support a command. + * @throws WrongArguments if the arguments passed to the command are not + * sufficient or correct to execute the command. + * @throws CommandFailed if the module failed to execute a command. + */ + public function runCommand($command, $args, $extra); +} diff --git a/src/core/Directus/Console/Modules/UserModule.php b/src/core/Directus/Console/Modules/UserModule.php new file mode 100644 index 0000000000..f435c9157d --- /dev/null +++ b/src/core/Directus/Console/Modules/UserModule.php @@ -0,0 +1,73 @@ +help = [ + 'password' => '' + . PHP_EOL . "\t\t-e " . 'User e-mail address.' + . PHP_EOL . "\t\t-p " . 'New password for the user.' + . PHP_EOL . "\t\t-d " . 'Directus path. Default: ' . $this->getBasePath() + ]; + + $this->commands_help = [ + 'password' => 'Change User Password: ' . PHP_EOL . PHP_EOL . "\t\t" + . $this->__module_name . ':password -e user_email -p new_password -d directus_path' . PHP_EOL + ]; + + $this->__module_name = 'user'; + $this->__module_description = 'commands to manage Directus users'; + } + + public function cmdPassword($args, $extra) + { + $directus_path = $this->getBasePath(); + + $data = []; + + foreach ($args as $key => $value) { + switch ($key) { + case 'e': + $data['user_email'] = $value; + break; + case 'p': + $data['user_password'] = $value; + break; + case 'd': + $directus_path = $value; + break; + } + } + + if (!isset($data['user_email'])) { + throw new WrongArgumentsException($this->__module_name . ':password ' . 'missing user e-mail to change password for!'); + } + + if (!isset($data['user_password'])) { + throw new WrongArgumentsException($this->__module_name . ':password ' . 'missing new password for user!'); + } + + $user = new User($directus_path); + try { + $user->changePassword($data['user_email'], $data['user_password']); + } catch (PasswordChangeException $ex) { + throw new CommandFailedException('Error changing user password' . ': ' . $ex->getMessage()); + } + + } +} diff --git a/src/core/Directus/Container/Container.php b/src/core/Directus/Container/Container.php new file mode 100644 index 0000000000..44e82de2ea --- /dev/null +++ b/src/core/Directus/Container/Container.php @@ -0,0 +1,37 @@ +offsetExists($offset); + } + + /** + * @inheritDoc + */ + public function get($offset) + { + if (!$this->offsetExists($offset)) { + throw new ValueNotFoundException(sprintf('The key "%s" is not defined.', $offset)); + } + + return $this->offsetGet($offset); + } + + /** + * @inheritDoc + */ + public function set($offset, $value) + { + $this->offsetSet($offset, $value); + } +} diff --git a/src/core/Directus/Container/Exception/ValueNotFoundException.php b/src/core/Directus/Container/Exception/ValueNotFoundException.php new file mode 100644 index 0000000000..cac2906c75 --- /dev/null +++ b/src/core/Directus/Container/Exception/ValueNotFoundException.php @@ -0,0 +1,8 @@ +=5.4.0", + "directus/collection": "^1.0", + "pimple/pimple": "^3.0" + }, + "autoload": { + "psr-4": { + "Directus\\Container\\": "" + } + } +} diff --git a/src/core/Directus/Database/Connection.php b/src/core/Directus/Database/Connection.php new file mode 100644 index 0000000000..4363ca7476 --- /dev/null +++ b/src/core/Directus/Database/Connection.php @@ -0,0 +1,81 @@ +getDriver()->getDatabasePlatformName(); + + switch (strtolower($driverName)) { + case 'mysql': + $enabled = $this->isMySQLStrictModeEnabled(); + break; + } + + return $enabled; + } + + /** + * Check if MySQL has Strict mode enabled + * + * @return bool + */ + protected function isMySQLStrictModeEnabled() + { + $strictModes = ['STRICT_ALL_TABLES', 'STRICT_TRANS_TABLES']; + $statement = $this->query('SELECT @@sql_mode as modes'); + $result = $statement->execute(); + $modesEnabled = $result->current(); + + $modes = explode(',', $modesEnabled['modes']); + foreach ($modes as $name) { + $modeName = strtoupper(trim($name)); + if (in_array($modeName, $strictModes)) { + return true; + } + } + + return false; + } + + /** + * Connect to the database + * + * @return \Zend\Db\Adapter\Driver\ConnectionInterface + */ + public function connect() + { + return call_user_func_array([$this->getDriver()->getConnection(), 'connect'], func_get_args()); + } + + /** + * Execute an query string + * + * @param $sql + * + * @return \Zend\Db\Adapter\Driver\StatementInterface|\Zend\Db\ResultSet\ResultSet + */ + public function execute($sql) + { + return $this->query($sql, static::QUERY_MODE_EXECUTE); + } +} diff --git a/src/core/Directus/Database/Ddl/Column/Bit.php b/src/core/Directus/Database/Ddl/Column/Bit.php new file mode 100644 index 0000000000..62213e1fb1 --- /dev/null +++ b/src/core/Directus/Database/Ddl/Column/Bit.php @@ -0,0 +1,13 @@ +setName($name); + $this->setNullable($nullable); + $this->setDefault($default); + $this->setOptions($options); + } +} diff --git a/src/core/Directus/Database/Ddl/Column/CollectionLength.php b/src/core/Directus/Database/Ddl/Column/CollectionLength.php new file mode 100644 index 0000000000..7b746b03c4 --- /dev/null +++ b/src/core/Directus/Database/Ddl/Column/CollectionLength.php @@ -0,0 +1,75 @@ +setLength($length); + + parent::__construct($name, $nullable, $default, $options); + } + + /** + * @param int $length + * @return self Provides a fluent interface + */ + public function setLength($length) + { + $values = $length; + if (!is_array($length)) { + $values = explode(',', $values); + } + + $length = implode(',', array_map(function ($value) { + return sprintf('"%s"', $value); + }, $values)); + + $this->length = $length; + + return $this; + } + + /** + * @return int + */ + public function getLength() + { + return $this->length; + } + + /** + * @return string + */ + protected function getLengthExpression() + { + return (string) $this->length; + } + + /** + * @return array + */ + public function getExpressionData() + { + $data = parent::getExpressionData(); + + if ($this->getLengthExpression()) { + $data[0][1][1] .= '(' . $this->getLengthExpression() . ')'; + } + + return $data; + } +} diff --git a/src/core/Directus/Database/Ddl/Column/Custom.php b/src/core/Directus/Database/Ddl/Column/Custom.php new file mode 100644 index 0000000000..488a26d5cb --- /dev/null +++ b/src/core/Directus/Database/Ddl/Column/Custom.php @@ -0,0 +1,43 @@ +setType($type); + } + + public function setType($type) + { + $this->type = $type; + } + + /** + * Sets the column length + * + * @param int|array $length + * + * @return $this + */ + public function setLength($length) + { + if (is_array($length)) { + $length = implode(',', array_map(function ($value) { + // add slashes in case the value has quotes + return sprintf('"%s"', addslashes($value)); + }, $length)); + } else { + $length = (int) $length; + } + + $this->length = $length; + + return $this; + } +} diff --git a/src/core/Directus/Database/Ddl/Column/Double.php b/src/core/Directus/Database/Ddl/Column/Double.php new file mode 100644 index 0000000000..c474144cf9 --- /dev/null +++ b/src/core/Directus/Database/Ddl/Column/Double.php @@ -0,0 +1,13 @@ +getLength(); + } +} diff --git a/src/core/Directus/Database/Exception/CollectionAlreadyExistsException.php b/src/core/Directus/Database/Exception/CollectionAlreadyExistsException.php new file mode 100644 index 0000000000..52a47c2da6 --- /dev/null +++ b/src/core/Directus/Database/Exception/CollectionAlreadyExistsException.php @@ -0,0 +1,15 @@ +query = $query; + } + + /** + * @return string + */ + public function getQuery() + { + return $this->query; + } +} diff --git a/src/core/Directus/Database/Exception/ItemNotFoundException.php b/src/core/Directus/Database/Exception/ItemNotFoundException.php new file mode 100644 index 0000000000..31cc7aa0d3 --- /dev/null +++ b/src/core/Directus/Database/Exception/ItemNotFoundException.php @@ -0,0 +1,15 @@ +identifier = $identifier; + $this->values = $values; + $this->not = $not; + $this->logic = $logic; + } + + /** + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * @return array + */ + public function getValue() + { + $operator = ($this->not ? 'not' : '') . 'in'; + + return [ + $operator => $this->values, + 'logical' => $this->logic + ]; + } + + /** + * @return array + */ + public function toArray() + { + return [ + $this->getIdentifier() => $this->getValue() + ]; + } +} diff --git a/src/core/Directus/Database/Query/Builder.php b/src/core/Directus/Database/Query/Builder.php new file mode 100644 index 0000000000..69de6b1fe9 --- /dev/null +++ b/src/core/Directus/Database/Query/Builder.php @@ -0,0 +1,818 @@ +connection = $connection; + } + + /** + * Sets the columns list to be selected + * + * @param array $columns + * + * @return $this + */ + public function columns(array $columns) + { + $this->columns = $columns; + + return $this; + } + + /** + * Gets the selected columns + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the from table + * + * @param $from + * + * @return $this + */ + public function from($from) + { + $this->from = $from; + + return $this; + } + + /** + * Gets the from table value + * + * @return null|string + */ + public function getFrom() + { + return $this->from; + } + + public function join($table, $on, $columns = ['*'], $type = 'inner') + { + $this->joins[] = compact('table', 'on', 'columns', 'type'); + + return $this; + } + + public function getJoins() + { + return $this->joins; + } + + /** + * Sets a condition to the query + * + * @param $column + * @param $operator + * @param $value + * @param $not + * @param $logical + * + * @return $this + */ + public function where($column, $operator = null, $value = null, $not = false, $logical = 'and') + { + if ($column instanceof \Closure) { + return $this->nestWhere($column, $logical); + } + + $type = 'basic'; + + $this->wheres[] = compact('type', 'operator', 'column', 'value', 'not', 'logical'); + + return $this; + } + + /** + * Creates a nested condition + * + * @param \Closure $callback + * @param string $logical + * + * @return $this + */ + public function nestWhere(\Closure $callback, $logical = 'and') + { + $query = $this->newQuery(); + call_user_func($callback, $query); + + if (count($query->getWheres())) { + $type = 'nest'; + $this->wheres[] = compact('type', 'query', 'logical'); + } + + return $this; + } + + public function nestOrWhere(\Closure $callback) + { + return $this->nestWhere($callback, 'or'); + } + + /** + * Create a condition where the given column is empty (NULL or empty string) + * + * @param $column + * + * @return Builder + */ + public function whereEmpty($column) + { + return $this->nestWhere(function(Builder $query) use ($column) { + $query->orWhereNull($column); + $query->orWhereEqualTo($column, ''); + }); + } + + /** + * Create a condition where the given column is NOT empty (NULL or empty string) + * + * @param $column + * + * @return Builder + */ + public function whereNotEmpty($column) + { + return $this->nestWhere(function(Builder $query) use ($column) { + $query->whereNotNull($column); + $query->whereNotEqualTo($column, ''); + }, 'and'); + } + + /** + * Sets a "where in" condition + * + * @param $column + * @param array|Builder $values + * @param bool $not + * @param string $logical + * + * @return Builder + */ + public function whereIn($column, $values, $not = false, $logical = 'and') + { + return $this->where($column, 'in', $values, $not, $logical); + } + + /** + * Sets a "where or in" condition + * + * @param $column + * @param array|Builder $values + * @param bool $not + * + * @return Builder + */ + public function orWhereIn($column, $values, $not = false) + { + return $this->where($column, 'in', $values, $not, 'or'); + } + + /** + * Sets an "where not in" condition + * + * @param $column + * @param array $values + * + * @return Builder + */ + public function whereNotIn($column, array $values) + { + return $this->whereIn($column, $values, true); + } + + public function whereBetween($column, array $values, $not = false) + { + return $this->where($column, 'between', $values, $not); + } + + public function whereNotBetween($column, array $values) + { + return $this->whereBetween($column, $values, true); + } + + public function whereEqualTo($column, $value, $not = false, $logical = 'and') + { + return $this->where($column, '=', $value, $not, $logical); + } + + public function orWhereEqualTo($column, $value, $not = false) + { + return $this->where($column, '=', $value, $not, 'or'); + } + + public function whereNotEqualTo($column, $value) + { + return $this->whereEqualTo($column, $value, true); + } + + public function whereLessThan($column, $value) + { + return $this->where($column, '<', $value); + } + + public function whereLessThanOrEqual($column, $value) + { + return $this->where($column, '<=', $value); + } + + public function whereGreaterThan($column, $value) + { + return $this->where($column, '>', $value); + } + + public function whereGreaterThanOrEqual($column, $value) + { + return $this->where($column, '>=', $value); + } + + public function whereNull($column, $not = false, $logical = 'and') + { + return $this->where($column, 'null', null, $not, $logical); + } + + public function orWhereNull($column, $not = false) + { + return $this->whereNull($column, $not, 'or'); + } + + public function whereNotNull($column) + { + return $this->whereNull($column, true); + } + + public function whereLike($column, $value, $not = false, $logical = 'and') + { + return $this->where($column, 'like', $value, $not, $logical); + } + + public function orWhereLike($column, $value, $not = false) + { + return $this->whereLike($column, $value, $not, 'or'); + } + + public function whereNotLike($column, $value) + { + return $this->whereLike($column, $value, true); + } + + public function whereAll($column, $table, $columnLeft, $columnRight, $values) + { + if ($columnLeft === null) { + $relation = new OneToManyRelation($this, $column, $table, $columnRight, $this->getFrom()); + } else { + $relation = new ManyToManyRelation($this, $table, $columnLeft, $columnRight); + } + + $relation->all($values); + + return $this->whereIn($column, $relation); + } + + public function whereHas($column, $table, $columnLeft, $columnRight, $count = 1, $not = false) + { + if (is_null($columnLeft)) { + $relation = new OneToManyRelation($this, $column, $table, $columnRight, $this->getFrom()); + } else { + $relation = new ManyToManyRelation($this, $table, $columnLeft, $columnRight); + } + + // If checking if has 0, this case will be the opposite + // has = 0, NOT IN the record that has more than 0 + // not has = 0, IN the record that has more than 0 + if ($count < 1) { + $not = !$not; + } + + $relation->has($count); + + return $this->whereIn($column, $relation, $not); + } + + public function whereNotHas($column, $table, $columnLeft, $columnRight, $count = 1) + { + return $this->whereHas($column, $table, $columnLeft, $columnRight, $count, true); + } + + public function whereRelational($column, $table, $columnLeft, $columnRight = null, \Closure $callback = null, $logical = 'and') + { + if (is_callable($columnRight)) { + // $column: Relational Column + // $table: Related table + // $columnRight: Related table that points to $column + $callback = $columnRight; + $columnRight = $columnLeft; + $columnLeft = null; + $relation = new ManyToOneRelation($this, $columnRight, $table); + } else if (is_null($columnLeft)) { + $relation = new OneToManyRelation($this, $column, $table, $columnRight, $this->getFrom()); + } else { + $relation = new ManyToManyRelation($this, $table, $columnLeft, $columnRight); + } + + call_user_func($callback, $relation); + + return $this->whereIn($column, $relation, false, $logical); + } + + public function orWhereRelational($column, $table, $columnLeft, $columnRight = null, \Closure $callback = null) + { + return $this->whereRelational($column, $table, $columnLeft, $columnRight, $callback, 'or'); + } + + /** + * Gets the query conditions + * + * @return array + */ + public function getWheres() + { + return $this->wheres; + } + + /** + * Order the query by the given table + * + * @param $column + * @param string $direction + * + * @return Builder + */ + public function orderBy($column, $direction = 'ASC') + { + $this->order[(string) $column] = (string) $direction; + + return $this; + } + + /** + * Gets the sorts + * + * @return array + */ + public function getOrder() + { + return $this->order; + } + + /** + * Clears the order list + */ + public function clearOrder() + { + $this->order = []; + } + + /** + * Sets the number of records to skip + * + * @param $value + * + * @return Builder + */ + public function offset($value) + { + $this->offset = max(0, $value); + + return $this; + } + + /** + * Alias of Builder::offset + * + * @param $value + * + * @return Builder + */ + public function skip($value) + { + return $this->offset($value); + } + + /** + * Gets the query offset + * + * @return int|null + */ + public function getOffset() + { + return $this->offset; + } + + /** + * Alias of Builder::getOffset + * + * @return int|null + */ + public function getSkip() + { + return $this->getOffset(); + } + + /** + * Sets the query result limit + * + * @param $value + * + * @return $this + */ + public function limit($value) + { + // ============================================================================= + // LIMIT 0 quickly returns an empty set. + // This can be useful for checking the validity of a query. + // @see: http://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html + // ============================================================================= + if ($value >= 0) { + $this->limit = (int) $value; + } else { + $this->limit = null; + } + + return $this; + } + + /** + * Gets the query result limit + * + * @return int|null + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Sets Group by columns + * + * @param array|string $columns + */ + public function groupBy($columns) + { + if (!is_array($columns)) { + $columns = [$columns]; + } + + if ($this->groupBys === null) { + $this->groupBys = []; + } + + foreach($columns as $column) { + $this->groupBys[] = $column; + } + } + + /** + * Sets having + * + * @param $column + * @param $operator + * @param $value + * + * @return $this + */ + public function having($column, $operator = null, $value = null) + { + $this->havings[] = compact('column', 'operator', 'value'); + + return $this; + } + + /** + * Gets havings + * + * @return array|null + */ + public function getHavings() + { + return $this->havings; + } + + /** + * Build the Select Object + * + * @return \Zend\Db\Sql\Select + */ + public function buildSelect() + { + $select = $this->getSqlObject()->select($this->getFrom()); + $select->columns($this->getColumns()); + $select->order($this->buildOrder()); + + if ($this->getJoins() !== null) { + foreach($this->getJoins() as $join) { + $select->join($join['table'], $join['on'], $join['columns'], $join['type']); + } + } + + if ($this->getOffset() !== null && $this->getLimit() !== null) { + $select->offset($this->getOffset()); + } + + if ($this->getLimit() !== null) { + $select->limit($this->getLimit()); + } + + foreach ($this->getWheres() as $condition) { + $this->buildCondition($select->where, $condition); + } + + if ($this->groupBys !== null) { + $groupBys = []; + + foreach ($this->groupBys as $groupBy) { + $groupBys[] = $this->getIdentifier($groupBy); + } + + $select->group($groupBys); + } + + if ($this->getHavings() !== null) { + foreach ($this->getHavings() as $having) { + if ($having['column'] instanceof Expression) { + $expression = $having['column']; + $callback = function(Having $having) use ($expression) { + $having->addPredicate($expression); + }; + } else { + $callback = function(Having $havingObject) use ($having) { + $havingObject->addPredicate(new Operator($having['column'], $having['operator'], $having['value'])); + }; + } + + $select->having($callback); + } + } + + return $select; + } + + /** + * Executes the query + * + * @return AbstractResultSet + */ + public function get() + { + $sql = $this->getSqlObject(); + $select = $this->buildSelect(); + + $statement = $sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + $resultSet = new ResultSet($result); + $resultSet->initialize($result); + + return $resultSet; + } + + /** + * Gets the query string + * + * @return string + */ + public function getSql() + { + $sql = $this->getSqlObject(); + $select = $this->buildSelect(); + + return $sql->buildSqlString($select, $this->connection); + } + + /** + * Build the condition expressions + * + * @param Predicate $where + * @param array $condition + */ + protected function buildCondition(Predicate $where, array $condition) + { + $logical = strtoupper(ArrayUtils::get($condition, 'logical', 'and')); + + if (ArrayUtils::get($condition, 'type') === 'nest') { + /** @var Builder $query */ + $query = ArrayUtils::get($condition, 'query'); + if ($logical === 'OR') { + $where->or; + } + + $where = $where->nest(); + + foreach ($query->getWheres() as $condition) { + $query->from($this->getFrom()); + $query->buildCondition($where, $condition); + } + + $where->unnest(); + } else { + $where->addPredicate($this->buildConditionExpression($condition), $logical); + } + } + + protected function buildOrder() + { + $order = []; + foreach($this->getOrder() as $orderBy => $orderDirection) { + if ($orderBy === '?') { + $expression = new Expression('RAND()'); + } else { + $expression = sprintf('%s %s', $this->getIdentifier($orderBy), $orderDirection); + } + + $order[] = $expression; + } + + return $order; + } + + /** + * Get the column identifier (table name prepended) + * + * @param string $column + * + * @return string + */ + protected function getIdentifier($column) + { + $platform = $this->getConnection()->getPlatform(); + $table = $this->getFrom(); + + if (strpos($column, $platform->getIdentifierSeparator()) === false) { + $column = implode($platform->getIdentifierSeparator(), [$table, $column]); + } + + return $column; + } + + protected function buildConditionExpression($condition) + { + $not = ArrayUtils::get($condition, 'not', false) === true; + $notChar = ''; + if ($not === true) { + $notChar = $condition['operator'] === '=' ? '!' : 'n'; + } + + $operator = $notChar . $condition['operator']; + + $column = $condition['column']; + $identifier = $this->getIdentifier($column); + $value = $condition['value']; + + if ($value instanceof Builder) { + $value = $value->buildSelect(); + } + + switch ($operator) { + case 'in': + $expression = new In($identifier, $value); + break; + case 'nin': + $expression = new NotIn($identifier, $value); + break; + case 'like': + $value = "%$value%"; + $expression = new Like($identifier, $value); + break; + case 'nlike': + $value = "%$value%"; + $expression = new NotLike($identifier, $value); + break; + case 'null': + $expression = new IsNull($identifier); + break; + case 'nnull': + $expression = new IsNotNull($identifier); + break; + case 'between': + $expression = new Between($identifier, array_shift($value), array_pop($value)); + break; + case 'nbetween': + $expression = new NotBetween($identifier, array_shift($value), array_pop($value)); + break; + default: + $expression = new Operator($identifier, $operator, $value); + } + + return $expression; + } + + protected function getSqlObject() + { + if ($this->sql === null) { + $this->sql = new Sql($this->connection); + } + + return $this->sql; + } + + /** + * Gets a new instance of the query builder + * + * @return \Directus\Database\Query\Builder + */ + public function newQuery() + { + return new self($this->connection); + } + + /** + * Gets the connection + * + * @return AdapterInterface + */ + public function getConnection() + { + return $this->connection; + } +} diff --git a/src/core/Directus/Database/Query/Relations/ManyToManyRelation.php b/src/core/Directus/Database/Query/Relations/ManyToManyRelation.php new file mode 100644 index 0000000000..ddce6126ce --- /dev/null +++ b/src/core/Directus/Database/Query/Relations/ManyToManyRelation.php @@ -0,0 +1,45 @@ +getConnection()); + + $this->parentBuilder = $builder; + $this->table = $table; + $this->columnLeft = $columnLeft; + $this->columnRight = $columnRight; + + $this->from($table); + } + + public function all(array $values) + { + $this->columns([$this->columnLeft]); + $this->whereIn($this->columnRight, $values); + $this->groupBy($this->columnLeft); + $this->having(new Expression('COUNT(*) = ?', count($values))); + + return $this; + } + + public function has($count = 1) + { + $this->columns([$this->columnLeft]); + $this->groupBy($this->columnLeft); + $this->having(new Expression('COUNT(*) >= ?', (int) $count)); + + return $this; + } +} \ No newline at end of file diff --git a/src/core/Directus/Database/Query/Relations/ManyToOneRelation.php b/src/core/Directus/Database/Query/Relations/ManyToOneRelation.php new file mode 100644 index 0000000000..cb19ee998f --- /dev/null +++ b/src/core/Directus/Database/Query/Relations/ManyToOneRelation.php @@ -0,0 +1,26 @@ +getConnection()); + + $this->parentBuilder = $builder; + $this->column = $column; + $this->relatedTable = $relatedTable; + + $this->columns([$this->column]); + $this->from($this->relatedTable); + } +} diff --git a/src/core/Directus/Database/Query/Relations/OneToManyRelation.php b/src/core/Directus/Database/Query/Relations/OneToManyRelation.php new file mode 100644 index 0000000000..938f6b4085 --- /dev/null +++ b/src/core/Directus/Database/Query/Relations/OneToManyRelation.php @@ -0,0 +1,65 @@ +getConnection()); + + $this->parentBuilder = $builder; + $this->column = $column; + $this->table = $table; + $this->columnRight = $columnRight; + $this->relatedTable = $relatedTable; + + $this->columns([$this->column]); + $this->from($relatedTable); + $on = sprintf('%s.%s = %s.%s', $this->relatedTable, $this->column, $this->table, $this->columnRight); + $this->join($this->table, $on, [$this->columnRight], 'right'); + } + + public function all(array $values) + { + $this->columns([]); + $this->whereIn($this->table . '.' . $this->column, $values); + $this->groupBy($this->columnRight); + $this->having(new Expression('COUNT(*) = ?', count($values))); + + return $this; + } + + public function has($count = 1) + { + $this->columns([]); + $this->groupBy($this->columnRight); + $this->having(new Expression('COUNT(*) >= ?', (int) $count)); + + return $this; + } + + public function whereLike($column, $value, $not = false, $logical = 'and') + { + $this->columns([]); + parent::whereLike($this->table . '.' . $column, $value, $not, $logical); + + return $this; + } + + public function orWhereLike($column, $value, $not = false) + { + $this->whereLike($column, $value, $not, 'or'); + + return $this; + } +} diff --git a/src/core/Directus/Database/README.md b/src/core/Directus/Database/README.md new file mode 100644 index 0000000000..503da852f9 --- /dev/null +++ b/src/core/Directus/Database/README.md @@ -0,0 +1,5 @@ +# Directus Database + +The Directus Database component. + +### _**A Work in Progress**_ diff --git a/src/core/Directus/Database/Repositories/AbstractRepository.php b/src/core/Directus/Database/Repositories/AbstractRepository.php new file mode 100644 index 0000000000..04e94824c1 --- /dev/null +++ b/src/core/Directus/Database/Repositories/AbstractRepository.php @@ -0,0 +1,60 @@ +tableGateway = $tableGateway; + $this->idAttribute = ArrayUtils::get($options, 'id', 'id'); + $this->hookEmitter = ArrayUtils::get($options, 'hookEmitter'); + $this->acl = ArrayUtils::get($options, 'acl'); + } + + /** + * @param string $attribute + * @param mixed $value + * @return mixed + */ + public function findOneBy($attribute, $value) + { + // TODO: Implement findOneBy() method. + } + + /** + * @param int|string $id + * @return mixed + */ + public function find($id) + { + return $this->findOneBy($this->idAttribute, $id); + } +} diff --git a/src/core/Directus/Database/Repositories/Repository.php b/src/core/Directus/Database/Repositories/Repository.php new file mode 100644 index 0000000000..0243010a70 --- /dev/null +++ b/src/core/Directus/Database/Repositories/Repository.php @@ -0,0 +1,8 @@ +items = $data; + + return $this; + } +} diff --git a/src/core/Directus/Database/ResultSet.php b/src/core/Directus/Database/ResultSet.php new file mode 100644 index 0000000000..1645c8dbf2 --- /dev/null +++ b/src/core/Directus/Database/ResultSet.php @@ -0,0 +1,155 @@ +initialize($dataSource); + } + } + + /** + * @inheritDoc + */ + public function initialize($dataSource) + { + if (is_array($dataSource)) { + $first = current($dataSource); + reset($dataSource); + $this->fieldCount = count($first); + $this->dataSource = new \ArrayIterator($dataSource); + } else { + $this->dataSource = $dataSource; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function getFieldCount() + { + return $this->dataSource->getFieldCount(); + } + + /** + * @inheritDoc + */ + public function count() + { + return $this->dataSource->count(); + } + + /** + * @inheritDoc + */ + public function current() + { + return new ResultItem($this->dataSource->current()); + } + + /** + * @inheritDoc + */ + public function next() + { + return $this->dataSource->next(); + } + + /** + * @inheritDoc + */ + public function key() + { + return $this->dataSource->key(); + } + + /** + * @inheritDoc + */ + public function valid() + { + return $this->dataSource->valid(); + } + + /** + * @inheritDoc + */ + public function rewind() + { + $this->dataSource->rewind(); + } + + /** + * @return array + */ + public function toArray() + { + return iterator_to_array($this->dataSource); + } + + /** + * @inheritDoc + */ + public function buffer() + { + return $this->dataSource->buffer(); + } + + /** + * @inheritDoc + */ + public function isBuffered() + { + return $this->dataSource->isBuffered(); + } + + /** + * @inheritDoc + */ + public function isQueryResult() + { + return $this->dataSource->isQueryResult(); + } + + /** + * @inheritDoc + */ + public function getAffectedRows() + { + return $this->dataSource->getAffectedRows(); + } + + /** + * @inheritDoc + */ + public function getGeneratedValue() + { + return $this->dataSource->getGeneratedValue(); + } + + /** + * @inheritDoc + */ + public function getResource() + { + return $this->dataSource->getResource(); + } +} diff --git a/src/core/Directus/Database/RowGateway/BaseRowGateway.php b/src/core/Directus/Database/RowGateway/BaseRowGateway.php new file mode 100644 index 0000000000..4d656982c3 --- /dev/null +++ b/src/core/Directus/Database/RowGateway/BaseRowGateway.php @@ -0,0 +1,346 @@ +acl = $acl; + + parent::__construct($primaryKeyColumn, $table, $adapterOrSql); + } + + public function getId() + { + return $this->data[$this->primaryKeyColumn[0]]; + } + + public function getCollection() + { + return $this->table; + } + + /** + * Override this function to do table-specific record data filtration, pre-insert and update. + * This method is called during #populate and #populateSkipAcl. + * + * @param array $rowData + * @param boolean $rowExistsInDatabase + * + * @return array Filtered $rowData. + */ + public function preSaveDataHook(array $rowData, $rowExistsInDatabase = false) + { + // Custom gateway logic + return $rowData; + } + + /** + * + * @param string|array $primaryKeyColumn + * @param $table + * @param $adapter + * @param Acl|null $acl + * + * @return BaseRowGateway + */ + public static function makeRowGatewayFromTableName($primaryKeyColumn, $table, $adapter, $acl = null) + { + + // ============================================================================= + // @NOTE: Setting the column to 'id' by default + // As it mostly will be the default column + // Otherwise it will be set to whatever name or compose id is. + // ============================================================================= + + // Underscore to camelcase table name to namespaced row gateway classname, + // e.g. directus_users => \Directus\Database\RowGateway\DirectusUsersRowGateway + $rowGatewayClassName = Formatting::underscoreToCamelCase($table) . 'RowGateway'; + $rowGatewayClassName = __NAMESPACE__ . '\\' . $rowGatewayClassName; + if (!class_exists($rowGatewayClassName)) { + $rowGatewayClassName = get_called_class(); + } + + return new $rowGatewayClassName($primaryKeyColumn, $table, $adapter, $acl); + } + + /** + * @param array $primaryKeyData + * + * @return string + */ + public static function stringifyPrimaryKeyForRecordDebugRepresentation($primaryKeyData) + { + if (null === $primaryKeyData) { + return 'null primary key'; + } + + return 'primary key (' . implode(':', array_keys($primaryKeyData)) . ') "' . implode(':', $primaryKeyData) . '"'; + } + + /** + * Populate Data + * + * @param array $rowData + * @param bool $rowExistsInDatabase + * + * @return RowGatewayInterface + */ + public function populate(array $rowData, $rowExistsInDatabase = false) + { + // IDEAL OR SOMETHING LIKE IT + // grab record + // populate skip acl + // diff btwn real record $rowData parameter + // only run blacklist on the diff from real data and the db data + + $rowData = $this->preSaveDataHook($rowData, $rowExistsInDatabase); + + //if(!$this->acl->hasTablePrivilege($this->table, 'bigedit')) { + // Enforce field write blacklist + // $attemptOffsets = array_keys($rowData); + // $this->acl->enforceBlacklist($this->table, $attemptOffsets, Acl::FIELD_WRITE_BLACKLIST); + //} + + return parent::populate($rowData, $rowExistsInDatabase); + } + + /** + * ONLY USE THIS FOR INITIALIZING THE ROW OBJECT. + * + * This function does not enforce ACL write privileges. + * It shouldn't be used to fulfill data assignment on behalf of the user. + * + * @param mixed $rowData Row key/value pairs. + * @param bool $rowExistsInDatabase + * + * @return RowGatewayInterface + */ + public function populateSkipAcl(array $rowData, $rowExistsInDatabase = false) + { + return parent::populate($rowData, $rowExistsInDatabase); + } + + /** + * ONLY USE THIS FOR INITIALIZING THE ROW OBJECT. + * + * This function does not enforce ACL write privileges. + * It shouldn't be used to fulfill data assignment on behalf of the user. + * @param mixed $rowData Row key/value pairs. + * + * @return RowGatewayInterface + */ + public function exchangeArray($rowData) + { + // NOTE: Something we made select where the primary key is not involved + // getting the read/unread/total from messages is an example + $exists = ArrayUtils::contains($rowData, $this->primaryKeyColumn); + + return $this->populateSkipAcl($rowData, $exists); + } + + /** + * @return int + * + * @throws ForbiddenCollectionUpdateException + * @throws \Exception + */ + public function save() + { + // if (!$this->acl) { + return parent::save(); + // } + + // ============================================================================= + // ACL Enforcement + // ----------------------------------------------------------------------------- + // Note: Field Write Blacklists are enforced at the object setter level + // BaseRowGateway::__set, BaseRowGateway::populate, BaseRowGateway::offsetSet) + // ============================================================================= + + $isCreating = !$this->rowExistsInDatabase(); + if ($isCreating) { + $this->acl->enforceCreate($this->table); + } + + // Enforce Privilege: "Little" Edit (I am the record CMS owner) + $ownerFieldName = SchemaService::getCollectionOwnerFieldName($this->table); + $cmsOwnerId = $this->acl->getRecordCmsOwnerId($this, $this->table); + $currentUserId = $this->acl->getUserId(); + $canEdit = $this->acl->canUpdate($this->table); + $canBigEdit = $this->acl->hasTablePrivilege($this->table, 'bigedit'); + + // Enforce Privilege: "Big" Edit (I am not the record CMS owner) + if ($cmsOwnerId !== $currentUserId && !$canBigEdit) { + $recordPk = self::stringifyPrimaryKeyForRecordDebugRepresentation($this->primaryKeyData); + $recordOwner = (false === $cmsOwnerId) ? 'no magic owner column' : 'the CMS owner #' . $cmsOwnerId; + $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); + + throw new ForbiddenCollectionUpdateException($aclErrorPrefix . 'Table bigedit access forbidden on `' . $this->table . '` table record with ' . $recordPk . ' and ' . $recordOwner . '.'); + } + + if (!$canEdit) { + $recordPk = self::stringifyPrimaryKeyForRecordDebugRepresentation($this->primaryKeyData); + $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); + + throw new ForbiddenCollectionUpdateException($aclErrorPrefix . 'Table edit access forbidden on `' . $this->table . '` table record with ' . $recordPk . ' owned by the authenticated CMS user (#' . $cmsOwnerId . ').'); + } + + try { + return parent::save(); + } catch (InvalidQueryException $e) { + throw new \Exception('Error running save on this data: ' . print_r($this->data, true)); + } + } + + public function delete() + { + if (!$this->acl) { + return parent::delete(); + } + + // ============================================================================= + // ACL Enforcement + // ============================================================================= + $currentUserId = $this->acl->getUserId(); + $cmsOwnerId = $this->acl->getRecordCmsOwnerId($this, $this->table); + $canDelete = $this->acl->hasTablePrivilege($this->table, 'delete'); + $canBigDelete = $this->acl->hasTablePrivilege($this->table, 'bigdelete'); + + // ============================================================================= + // Enforce Privilege: "Big" Delete (I am not the record CMS owner) + // ============================================================================= + if ($cmsOwnerId !== $currentUserId && !$canBigDelete) { + $recordPk = self::stringifyPrimaryKeyForRecordDebugRepresentation($this->primaryKeyData); + $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); + + throw new ForbiddenCollectionDeleteException($aclErrorPrefix . 'Table harddelete access forbidden on `' . $this->table . '` table record with ' . $recordPk . ' owned by the authenticated CMS user (#' . $cmsOwnerId . ').'); + } + + // ============================================================================= + // Enforce Privilege: "Little" Delete (I am the record CMS owner) + // ============================================================================= + if (!$canDelete) { + $recordPk = self::stringifyPrimaryKeyForRecordDebugRepresentation($this->primaryKeyData); + $recordOwner = (false === $cmsOwnerId) ? 'no magic owner column' : 'the CMS owner #' . $cmsOwnerId; + $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); + + throw new ForbiddenCollectionDeleteException($aclErrorPrefix . 'Table bigharddelete access forbidden on `' . $this->table . '` table record with ' . $recordPk . ' and ' . $recordOwner . '.'); + } + + return parent::delete(); + } + + /** + * __get + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + // Confirm user group has read privileges on field with name $name + if ($this->acl) { + $this->acl->enforceReadField($this->table, $name); + } + + return parent::__get($name); + } + + /** + * Offset get + * + * @param string $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + // Confirm user group has read privileges on field with name $offset + if ($this->acl) { + $this->acl->enforceReadField($this->table, $offset); + } + + return parent::offsetGet($offset); + } + + /** + * Offset set + * + * NOTE: Protecting this method protects self#__set, which calls this method in turn. + * + * @param string $offset + * @param mixed $value + * + * @return RowGatewayInterface + */ + public function offsetSet($offset, $value) + { + // Enforce field write blacklist + if ($this->acl) { + $this->acl->enforceWriteField($this->table, $offset); + } + + return parent::offsetSet($offset, $value); + } + + /** + * Offset unset + * + * @param string $offset + * + * @return RowGatewayInterface + */ + public function offsetUnset($offset) + { + // Enforce field write blacklist + if ($this->acl) { + $this->acl->enforceWriteField($this->table, $offset); + } + + return parent::offsetUnset($offset); + } +} diff --git a/src/core/Directus/Database/RowGateway/DirectusMediaRowGateway.php b/src/core/Directus/Database/RowGateway/DirectusMediaRowGateway.php new file mode 100644 index 0000000000..4e9b02432f --- /dev/null +++ b/src/core/Directus/Database/RowGateway/DirectusMediaRowGateway.php @@ -0,0 +1,22 @@ +acl->getCmsOwnerColumnByTable($this->table); + // $rowData[$cmsOwnerColumnName] = $currentUser['id']; + } else { + if (array_key_exists('date_uploaded', $rowData)) + unset($rowData['date_uploaded']); + } + return $rowData; + } + +} diff --git a/src/core/Directus/Database/RowGateway/DirectusUsersRowGateway.php b/src/core/Directus/Database/RowGateway/DirectusUsersRowGateway.php new file mode 100644 index 0000000000..5457f7bff3 --- /dev/null +++ b/src/core/Directus/Database/RowGateway/DirectusUsersRowGateway.php @@ -0,0 +1,33 @@ +table, $this->sql->getAdapter(), $this->acl); + $dbRecord = $TableGateway->find($rowData['id']); + if (false === $dbRecord) { + // @todo is it better to throw an exception here? + $rowExistsInDatabase = false; + } + } + + // User is updating themselves. + // Corresponds to a ping indicating their last activity. + // Updated their "last_access" value. + if ($this->acl) { + if (isset($rowData['id']) && $rowData['id'] == $this->acl->getUserId()) { + $rowData['last_access'] = DateTimeUtils::nowInUTC()->toString(); + } + } + + return $rowData; + } + +} diff --git a/src/core/Directus/Database/Schema/DataTypes.php b/src/core/Directus/Database/Schema/DataTypes.php new file mode 100644 index 0000000000..2bfc17a92c --- /dev/null +++ b/src/core/Directus/Database/Schema/DataTypes.php @@ -0,0 +1,381 @@ +attributes = new Attributes($attributes); + } + + /** + * @param mixed $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return $this->attributes->has($offset); + } + + /** + * @param mixed $offset + * + * @return mixed + */ + public function offsetGet($offset) + { + return $this->attributes->get($offset); + } + + /** + * @param mixed $offset + * @param mixed $value + * + * @return void + * + * @throws ErrorException + */ + public function offsetSet($offset, $value) + { + throw new ErrorException('Cannot set any value in ' . get_class($this)); + } + + /** + * @param mixed $offset + * + * @return void + * + * @throws ErrorException + */ + public function offsetUnset($offset) + { + throw new ErrorException('Cannot unset any attribute in ' . get_class($this)); + } + + /** + * @return array + */ + public function toArray() + { + return $this->attributes->toArray(); + } + + /** + * @inheritDoc + */ + function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/src/core/Directus/Database/Schema/Object/Collection.php b/src/core/Directus/Database/Schema/Object/Collection.php new file mode 100644 index 0000000000..ee08f47653 --- /dev/null +++ b/src/core/Directus/Database/Schema/Object/Collection.php @@ -0,0 +1,602 @@ +attributes->get('collection'); + } + + /** + * Sets the collection fields + * + * @param array $fields + * + * @return Collection + */ + public function setFields(array $fields) + { + foreach ($fields as $field) { + if (is_array($field)) { + $field = new Field($field); + } + + if (!($field instanceof Field)) { + throw new \InvalidArgumentException('Invalid field object. ' . gettype($field) . ' given instead'); + } + + // @NOTE: This is a temporary solution + // to always set the primary field to the first primary key field + if (!$this->getPrimaryField() && $field->hasPrimaryKey()) { + $this->primaryField = $field; + } else if (!$this->getSortingField() && $field->isSortingType()) { + $this->sortingField = $field; + } else if (!$this->getStatusField() && $field->isStatusType()) { + $this->statusField = $field; + } else if (!$this->getDateCreatedField() && $field->isDateCreatedType()) { + $this->dateCreatedField = $field; + } else if (!$this->getUserCreatedField() && $field->isUserCreatedType()) { + $this->userCreatedField = $field;; + } else if (!$this->getDateModifiedField() && $field->isDateModifiedType()) { + $this->dateModifiedField = $field; + } else if (!$this->getUserModifiedField() && $field->isUserModifiedType()) { + $this->userModifiedField = $field; + } + + $this->fields[$field->getName()] = $field; + } + + return $this; + } + + /** + * Gets a list of the collection's fields + * + * @param array $names + * + * @return Field[] + */ + public function getFields(array $names = []) + { + $fields = $this->fields; + + if ($names) { + $fields = array_filter($fields, function(Field $field) use ($names) { + return in_array($field->getName(), $names); + }); + } + + return $fields; + } + + /** + * Returns a list of Fields not in the given list name + * + * @param array $names + * + * @return Field[] + */ + public function getFieldsNotIn(array $names) + { + $fields = $this->fields; + + if ($names) { + $fields = array_filter($fields, function(Field $field) use ($names) { + return !in_array($field->getName(), $names); + }); + } + + return $fields; + } + + /** + * Gets a field with the given name + * + * @param string $name + * + * @return Field + */ + public function getField($name) + { + $fields = $this->getFields([$name]); + + // Gets the first matched result + return array_shift($fields); + } + + /** + * Checks whether the collection is being managed by Directus + * + * @return bool + */ + public function isManaged() + { + return $this->attributes->get('managed') == 1; + } + + /** + * Get all fields data as array + * + * @return array + */ + public function getFieldsArray() + { + return array_map(function(Field $field) { + return $field->toArray(); + }, $this->getFields()); + } + + /** + * Returns an array representation a list of Fields not in the given list name + * + * @param array $names + * + * @return array + */ + public function getFieldsNotInArray(array $names) + { + return array_map(function (Field $field) { + return $field->toArray(); + }, $this->getFieldsNotIn($names)); + } + + /** + * Gets all relational fields + * + * @param array $names + * + * @return Field[] + */ + public function getRelationalFields(array $names = []) + { + return array_filter($this->getFields($names), function (Field $field) { + return $field->hasRelationship(); + }); + } + + /** + * Gets all relational fields + * + * @param array $names + * + * @return Field[] + */ + public function getNonRelationalFields(array $names = []) + { + return array_filter($this->getFields($names), function(Field $field) { + return !$field->hasRelationship(); + }); + } + + /** + * Gets all the alias fields + * + * @return Field[] + */ + public function getAliasFields() + { + return array_filter($this->getFields(), function(Field $field) { + return $field->isAlias(); + }); + } + + /** + * Gets all the non-alias fields + * + * @return Field[] + */ + public function getNonAliasFields() + { + return array_filter($this->getFields(), function(Field $field) { + return !$field->isAlias(); + }); + } + + /** + * Gets all the fields name + * + * @return array + */ + public function getFieldsName() + { + return array_map(function(Field $field) { + return $field->getName(); + }, $this->getFields()); + } + + /** + * Gets all the relational fields name + * + * @return array + */ + public function getRelationalFieldsName() + { + return array_map(function (Field $field) { + return $field->getName(); + }, $this->getRelationalFields()); + } + + /** + * Gets all the alias fields name + * + * @return array + */ + public function getAliasFieldsName() + { + return array_map(function(Field $field) { + return $field->getName(); + }, $this->getAliasFields()); + } + + /** + * Gets all the non-alias fields name + * + * @return array + */ + public function getNonAliasFieldsName() + { + return array_map(function(Field $field) { + return $field->getName(); + }, $this->getNonAliasFields()); + } + + /** + * Checks whether the collection has a `primary key` interface field + * + * @return bool + */ + public function hasPrimaryField() + { + return $this->getPrimaryField() ? true : false; + } + + /** + * Checks whether the collection has a `status` interface field + * + * @return bool + */ + public function hasStatusField() + { + return $this->getStatusField() ? true : false; + } + + /** + * Checks whether the collection has a `sorting` interface field + * + * @return bool + */ + public function hasSortingField() + { + return $this->getSortingField() ? true : false; + } + + /** + * Checks Whether or not the collection has the given field name + * + * @param string $name + * + * @return bool + */ + public function hasField($name) + { + return array_key_exists($name, $this->fields); + } + + /** + * Checks whether or not the collection has the given data type field + * + * @param string $type + * + * @return bool + */ + public function hasType($type) + { + foreach ($this->fields as $field) { + if (strtolower($type) === strtolower($field->getType())) { + return true; + } + } + + return false; + } + + /** + * Checks whether or not the collection has a JSON data type field + * + * @return bool + */ + public function hasJsonField() + { + return $this->hasType(DataTypes::TYPE_JSON) || $this->hasType(DataTypes::TYPE_LONG_JSON) || $this->hasType(DataTypes::TYPE_TINY_JSON) || $this->hasType(DataTypes::TYPE_MEDIUM_JSON); + } + + /** + * Checks whether or not the collection has a Array data type field + * + * @return bool + */ + public function hasArrayField() + { + return $this->hasType(DataTypes::TYPE_ARRAY); + } + + /** + * Checks whether or not the collection has a Boolean data type field + * + * @return bool + */ + public function hasBooleanField() + { + return $this->hasType(DataTypes::TYPE_BOOLEAN) || $this->hasType(DataTypes::TYPE_BOOL); + } + + /** + * Gets the schema/database this collection belongs to + * + * @return null|string + */ + public function getSchema() + { + return $this->attributes->get('schema', null); + } + + /** + * Whether or not the collection is hidden + * + * @return bool + */ + public function isHidden() + { + return (bool)$this->attributes->get('hidden'); + } + + /** + * Whether or not the collection is single + * + * @return bool + */ + public function isSingle() + { + return (bool) $this->attributes->get('single'); + } + + /** + * Gets the collection custom status mapping + * + * @return StatusMapping|null + */ + public function getStatusMapping() + { + $statusField = $this->getStatusField(); + if (!$statusField) { + return null; + } + + $mapping = $statusField->getOptions('status_mapping'); + if ($mapping === null) { + return $mapping; + } + + if ($mapping instanceof StatusMapping) { + return $mapping; + } + + if (!is_array($mapping)) { + $mapping = @json_decode($mapping, true); + } + + if (is_array($mapping)) { + $this->attributes->set('status_mapping', new StatusMapping($mapping)); + + $mapping = $this->attributes->get('status_mapping'); + } + + return $mapping; + } + + /** + * Gets primary key interface field + * + * @return Field + */ + public function getPrimaryField() + { + return $this->primaryField; + } + + /** + * Gets the primary key field + * + * @return Field|null + */ + public function getPrimaryKey() + { + $primaryKeyField = null; + + foreach ($this->getFields() as $field) { + if ($field->hasPrimaryKey()) { + $primaryKeyField = $field; + break; + } + } + + return $primaryKeyField; + } + + /** + * Gets primary key interface field's name + * + * @return string + */ + public function getPrimaryKeyName() + { + $primaryField = $this->getPrimaryKey(); + $name = null; + + if ($primaryField) { + $name = $primaryField->getName(); + } + + return $name; + } + + /** + * Gets status field + * + * @return Field|null + */ + public function getStatusField() + { + return $this->statusField; + } + + /** + * Gets the sort interface field + * + * @return Field|null + */ + public function getSortingField() + { + return $this->sortingField; + } + + /** + * Gets the field storing the record's user owner + * + * @return Field|bool + */ + public function getUserCreatedField() + { + return $this->userCreatedField; + } + + /** + * Gets the field storing the user updating the record + * + * @return Field|null + */ + public function getUserModifiedField() + { + return $this->userModifiedField; + } + + /** + * Gets the field storing the record created time + * + * @return Field|null + */ + public function getDateCreatedField() + { + return $this->dateCreatedField; + } + + /** + * Gets the field storing the record updated time + * + * @return Field|null + */ + public function getDateModifiedField() + { + return $this->dateModifiedField; + } + + /** + * Gets the collection comment + * + * @return string + */ + public function getNote() + { + return $this->attributes->get('comment'); + } + + /** + * Gets the item preview url + * + * @return string + */ + public function getPreviewUrl() + { + return $this->attributes->get('preview_url'); + } + + /** + * Gets Collection item name display template + * + * Representation value of the table items + * + * @return string + */ + public function getItemNameTemplate() + { + return $this->attributes->get('item_name_template'); + } + + /** + * Array representation of the collection with fields + * + * @return array + */ + public function toArray() + { + $attributes = parent::toArray(); + $attributes['fields'] = $this->getFieldsArray(); + + return $attributes; + } +} diff --git a/src/core/Directus/Database/Schema/Object/Field.php b/src/core/Directus/Database/Schema/Object/Field.php new file mode 100644 index 0000000000..420b9ed4ea --- /dev/null +++ b/src/core/Directus/Database/Schema/Object/Field.php @@ -0,0 +1,575 @@ +attributes->get('id'); + } + + /** + * Gets the field name + * + * @return string + */ + public function getName() + { + return $this->attributes->get('field'); + } + + /** + * Gets the field type + * + * @return string + */ + public function getType() + { + $type = $this->attributes->get('type'); + + // if the type if empty in Directus Fields table + // fallback to the actual data type + if (!$type) { + $type = $this->getOriginalType(); + } + + return $type; + } + + /** + * Gets the field original type (based on its database) + * + * @return string + */ + public function getOriginalType() + { + return $this->attributes->get('original_type'); + } + + /** + * Get the field length + * + * @return int + */ + public function getLength() + { + $length = $this->getCharLength(); + + if (!$length) { + $length = (int) $this->attributes->get('length'); + } + + return $length; + } + + /** + * Gets field full type (mysql) + * + * @return string + */ + public function getColumnType() + { + // TODO: Make this from the schema manager + return $this->attributes->get('column_type'); + } + + /** + * Checks whether the fields only accepts unsigned values + * + * @return bool + */ + public function isUnsigned() + { + $type = $this->getColumnType(); + + return strpos($type, 'unsigned') !== false; + } + + /** + * Checks whether the columns has zero fill attribute + * + * @return bool + */ + public function hasZeroFill() + { + $type = $this->getColumnType(); + + return strpos($type, 'zerofill') !== false; + } + + /** + * Gets the field character length + * + * @return int + */ + public function getCharLength() + { + return (int) $this->attributes->get('char_length'); + } + + /** + * Gets field precision + * + * @return int + */ + public function getPrecision() + { + return (int) $this->attributes->get('precision'); + } + + /** + * Gets field scale + * + * @return int + */ + public function getScale() + { + return (int) $this->attributes->get('scale'); + } + + /** + * Gets field ordinal position + * + * @return int + */ + public function getSort() + { + return (int) $this->attributes->get('sort'); + } + + /** + * Gets field default value + * + * @return mixed + */ + public function getDefaultValue() + { + return $this->attributes->get('default_value'); + } + + /** + * Gets whether or not the field is nullable + * + * @return bool + */ + public function isNullable() + { + return boolval($this->attributes->get('nullable')); + } + + /** + * Gets the field key + * + * @return string + */ + public function getKey() + { + return $this->attributes->get('key'); + } + + /** + * Gets the field extra + * + * @return string + */ + public function getExtra() + { + return $this->attributes->get('extra'); + } + + /** + * Gets whether or not the column has auto increment + * + * @return bool + */ + public function hasAutoIncrement() + { + return strtolower($this->getExtra() ?: '') === 'auto_increment'; + } + + /** + * Checks whether or not the field has primary key + * + * @return bool + */ + public function hasPrimaryKey() + { + return strtoupper($this->getKey()) === 'PRI'; + } + + /** + * Checks whether or not the field has unique key + * + * @return bool + */ + public function hasUniqueKey() + { + return strtoupper($this->getKey()) === 'UNI'; + } + + /** + * Gets whether the field is required + * + * @return bool + */ + public function isRequired() + { + return $this->attributes->get('required'); + } + + /** + * Gets the interface name + * + * @return string + */ + public function getInterface() + { + return $this->attributes->get('interface'); + } + + /** + * Gets all or the given key options + * + * @param string|null $key + * + * @return mixed + */ + public function getOptions($key = null) + { + $options = []; + if ($this->attributes->has('options')) { + $options = $this->attributes->get('options'); + } + + if ($key !== null && is_array($options)) { + $options = ArrayUtils::get($options, $key); + } + + return $options; + } + + /** + * Gets whether the field must be hidden in lists + * + * @return bool + */ + public function isHiddenList() + { + return $this->attributes->get('hidden_list'); + } + + /** + * Gets whether the field must be hidden in forms + * + * @return bool + */ + public function isHiddenInput() + { + return $this->attributes->get('hidden_input'); + } + + /** + * Gets the field comment + * + * @return null|string + */ + public function getNote() + { + return $this->attributes->get('comment'); + } + + /** + * Gets the field regex pattern validation string + * + * @return string|null + */ + public function getValidation() + { + return $this->attributes->get('validation'); + } + + /** + * Gets the collection's name the field belongs to + * + * @return string + */ + public function getCollectionName() + { + return $this->attributes->get('collection'); + } + + /** + * Checks whether the field is an alias + * + * @return bool + */ + public function isAlias() + { + return DataTypes::isAliasType($this->getType()); + } + + /** + * Checks whether the field is a array type + * + * @return bool + */ + public function isArray() + { + return strtoupper($this->getType()) === static::TYPE_ARRAY; + } + + /** + * Checks whether the field is a json type + * + * @return bool + */ + public function isJson() + { + return in_array( + strtoupper($this->getType()), + [ + static::TYPE_JSON, + static::TYPE_TINY_JSON, + static::TYPE_MEDIUM_JSON, + static::TYPE_LONG_JSON + ] + ); + } + + /** + * Checks whether the field is a boolean type + * + * @return bool + */ + public function isBoolean() + { + return in_array( + strtoupper($this->getType()), + [ + strtoupper(DataTypes::TYPE_BOOLEAN), + strtoupper(DataTypes::TYPE_BOOL) + ] + ); + } + + /** + * Checks whether this column is date system interface + * + * @return bool + */ + public function isSystemDateType() + { + return DataTypes::isSystemDateType($this->getType()); + } + + /** + * Checks whether or not the field is a status type + * + * @return bool + */ + public function isStatusType() + { + return $this->isType(DataTypes::TYPE_STATUS); + } + + /** + * Checks whether or not the field is a sort type + * + * @return bool + */ + public function isSortingType() + { + return $this->isType(DataTypes::TYPE_SORT); + } + + /** + * Checks whether or not the field is a date created type + * + * @return bool + */ + public function isDateCreatedType() + { + return $this->isType(DataTypes::TYPE_DATETIME_CREATED); + } + + /** + * Checks whether or not the field is an user created type + * + * @return bool + */ + public function isUserCreatedType() + { + return $this->isType(DataTypes::TYPE_USER_CREATED); + } + + /** + * Checks whether or not the field is a date modified type + * + * @return bool + */ + public function isDateModifiedType() + { + return $this->isType(DataTypes::TYPE_DATETIME_MODIFIED); + } + + /** + * Checks whether or not the field is an user modified type + * + * @return bool + */ + public function isUserModifiedType() + { + return $this->isType(DataTypes::TYPE_USER_MODIFIED); + } + + /** + * Checks whether or not the field is the given type + * + * @param string $type + * + * @return bool + */ + public function isType($type) + { + return $type === strtolower($this->getType()); + } + + /** + * Set the column relationship + * + * @param FieldRelationship|array $relationship + * + * @return Field + */ + public function setRelationship($relationship) + { + // Ignore relationship information if the field is primary key + if (!$this->hasPrimaryKey()) { + // Relationship can be pass as an array + if (!($relationship instanceof FieldRelationship)) { + $relationship = new FieldRelationship($this, $relationship); + } + + $this->relationship = $relationship; + } + + return $this; + } + + /** + * Gets the field relationship + * + * @return FieldRelationship + */ + public function getRelationship() + { + return $this->relationship; + } + + /** + * Checks whether the field has relationship + * + * @return bool + */ + public function hasRelationship() + { + return $this->getRelationship() instanceof FieldRelationship; + } + + /** + * Gets the field relationship type + * + * @return null|string + */ + public function getRelationshipType() + { + $type = null; + + if ($this->hasRelationship()) { + $type = $this->getRelationship()->getType(); + } + + return $type; + } + + /** + * Checks whether the relationship is MANY TO ONE + * + * @return bool + */ + public function isManyToOne() + { + return $this->hasRelationship() ? $this->getRelationship()->isManyToOne() : false; + } + + /** + * Checks whether the relationship is MANY TO MANY + * + * @return bool + */ + public function isManyToMany() + { + return $this->hasRelationship() ? $this->getRelationship()->isManyToMany() : false; + } + + /** + * Checks whether the relationship is ONE TO MANY + * + * @return bool + */ + public function isOneToMany() + { + return $this->hasRelationship() ? $this->getRelationship()->isOneToMany() : false; + } + + /** + * Checks whether the field has ONE/MANY TO MANY Relationship + * + * @return bool + */ + public function isToMany() + { + return $this->isOneToMany() || $this->isManyToMany(); + } + + /** + * Is the field being managed by Directus + * + * @return bool + */ + public function isManaged() + { + return $this->attributes->get('managed') == 1; + } + + /** + * @return array + */ + public function toArray() + { + $attributes = parent::toArray(); + $attributes['relationship'] = $this->hasRelationship() ? $this->getRelationship()->toArray() : null; + + return $attributes; + } +} diff --git a/src/core/Directus/Database/Schema/Object/FieldRelationship.php b/src/core/Directus/Database/Schema/Object/FieldRelationship.php new file mode 100644 index 0000000000..4b4680f978 --- /dev/null +++ b/src/core/Directus/Database/Schema/Object/FieldRelationship.php @@ -0,0 +1,233 @@ +fromField = $fromField; + + parent::__construct($attributes); + + if ($this->fromField->getName() === $this->attributes->get('field_b')) { + $this->attributes->replace( + $this->swapRelationshipAttributes($this->attributes->toArray()) + ); + } + + $this->attributes->set('type', $this->guessType()); + } + + /** + * Gets the parent collection + * + * @return string + */ + public function getCollectionA() + { + return $this->attributes->get('collection_a'); + } + + /** + * Gets the parent field + * + * @return string + */ + public function getFieldA() + { + return $this->attributes->get('field_a'); + } + + /** + * + * + * @return null|string + */ + public function getJunctionKeyA() + { + return $this->attributes->get('junction_key_a'); + } + + public function getJunctionCollection() + { + return $this->attributes->get('junction_collection'); + } + + public function getJunctionMixedCollections() + { + return $this->attributes->get('junction_mixed_collections'); + } + + public function getJunctionKeyB() + { + return $this->attributes->get('junction_key_b'); + } + + public function getCollectionB() + { + return $this->attributes->get('collection_b'); + } + + public function getFieldB() + { + return $this->attributes->get('field_b'); + } + + /** + * Checks whether the relationship has a valid type + * + * @return bool + */ + public function isValid() + { + return $this->getType() !== null; + } + + /** + * Gets the relationship type + * + * @return string|null + */ + public function getType() + { + return $this->attributes->get('type'); + } + + /** + * Checks whether the relatiopship is MANY TO ONE + * + * @return bool + */ + public function isManyToOne() + { + return $this->getType() === static::MANY_TO_ONE; + } + + /** + * Checks whether the relatiopship is MANY TO MANY + * + * @return bool + */ + public function isManyToMany() + { + return $this->getType() === static::MANY_TO_MANY; + } + + /** + * Checks whether the relatiopship is ONE TO MANY + * + * @return bool + */ + public function isOneToMany() + { + return $this->getType() === static::ONE_TO_MANY; + } + + /** + * Checks whether is a many to many or one to many + * + * @return bool + */ + public function isToMany() + { + return $this->isManyToMany() || $this->isOneToMany(); + } + + /** + * Guess the data type + * + * @return null|string + */ + protected function guessType() + { + $fieldName = $this->fromField->getName(); + $isAlias = $this->fromField->isAlias(); + $type = null; + + if (!$this->fromField) { + $type = null; + } else if ( + !$isAlias && + $this->getCollectionB() !== null && + $this->getFieldA() === $fieldName && + $this->getJunctionKeyA() === null && + $this->getJunctionCollection() === null && + $this->getJunctionKeyB() === null && + $this->getJunctionMixedCollections() === null && + $this->getCollectionB() !== null + // Can have or not this value depends if the backward (O2M) relationship is set + // $this->getFieldB() === null + ) { + $type = static::MANY_TO_ONE; + } else if ( + $isAlias && + $this->getCollectionB() !== null && + $this->getFieldA() === $fieldName && + $this->getJunctionKeyA() === null && + $this->getJunctionCollection() === null && + $this->getJunctionKeyB() === null && + $this->getJunctionMixedCollections() === null && + $this->getCollectionB() !== null && + $this->getFieldB() !== null + ) { + $type = static::ONE_TO_MANY; + } else if ( + $isAlias && + $this->getCollectionB() !== null && + $this->getFieldA() === $fieldName && + $this->getJunctionKeyA() !== null && + $this->getJunctionCollection() !== null && + $this->getJunctionKeyB() !== null && + $this->getJunctionMixedCollections() === null && + $this->getCollectionB() !== null + // $this->getFieldB() !== null + ) { + $type = static::MANY_TO_MANY; + } + + return $type; + } + + /** + * Change the direction of the relationship + * + * @param array $attributes + * + * @return array + */ + protected function swapRelationshipAttributes(array $attributes) + { + $newAttributes = [ + 'collection_a' => ArrayUtils::get($attributes, 'collection_b'), + 'field_a' => ArrayUtils::get($attributes, 'field_b'), + 'junction_key_a' => ArrayUtils::get($attributes, 'junction_key_b'), + 'junction_collection' => ArrayUtils::get($attributes, 'junction_collection'), + 'junction_mixed_collections' => ArrayUtils::get($attributes, 'junction_mixed_collections'), + 'junction_key_b' => ArrayUtils::get($attributes, 'junction_key_a'), + 'collection_b' => ArrayUtils::get($attributes, 'collection_a'), + 'field_b' => ArrayUtils::get($attributes, 'field_a'), + ]; + + return $newAttributes; + } +} diff --git a/src/core/Directus/Database/Schema/SchemaFactory.php b/src/core/Directus/Database/Schema/SchemaFactory.php new file mode 100644 index 0000000000..46e7a435b0 --- /dev/null +++ b/src/core/Directus/Database/Schema/SchemaFactory.php @@ -0,0 +1,415 @@ +schemaManager = $manager; + $this->validator = new Validator(); + } + + /** + * Create a new table + * + * @param string $name + * @param array $columnsData + * + * @return CreateTable + */ + public function createTable($name, array $columnsData = []) + { + $table = new CreateTable($name); + $columns = $this->createColumns($columnsData); + + foreach ($columnsData as $column) { + if (ArrayUtils::get($column, 'primary_key', false)) { + $table->addConstraint(new PrimaryKey($column['field'])); + } else if (ArrayUtils::get($column, 'unique') == true) { + $table->addConstraint(new UniqueKey($column['field'])); + } + } + + foreach ($columns as $column) { + $table->addColumn($column); + } + + return $table; + } + + /** + * Alter an existing table + * + * @param $name + * @param array $data + * + * @return AlterTable + * + * @throws FieldAlreadyHasUniqueKeyException + */ + public function alterTable($name, array $data) + { + $table = new AlterTable($name); + + $collection = $this->schemaManager->getCollection($name); + + $toAddColumnsData = ArrayUtils::get($data, 'add', []); + $toAddColumns = $this->createColumns($toAddColumnsData); + foreach ($toAddColumns as $column) { + $table->addColumn($column); + + $options = $column->getOptions(); + if (is_array($options) && ArrayUtils::get($options, 'unique') == true) { + $table->addConstraint(new UniqueKey($column->getName())); + } + } + + $toChangeColumnsData = ArrayUtils::get($data, 'change', []); + $toChangeColumns = $this->createColumns($toChangeColumnsData); + foreach ($toChangeColumns as $column) { + $table->changeColumn($column->getName(), $column); + + $field = $collection->getField($column->getName()); + if ($field->hasUniqueKey()) { + throw new FieldAlreadyHasUniqueKeyException($column->getName()); + } + + $options = $column->getOptions(); + if (is_array($options) && ArrayUtils::get($options, 'unique') == true) { + $table->addConstraint(new UniqueKey($column->getName())); + } + } + + $toDropColumnsName = ArrayUtils::get($data, 'drop', []); + foreach ($toDropColumnsName as $column) { + $table->dropColumn($column); + } + + return $table; + } + + /** + * @param array $data + * + * @return Column[] + */ + public function createColumns(array $data) + { + $columns = []; + foreach ($data as $column) { + if (!DataTypes::isAliasType(ArrayUtils::get($column, 'type'))) { + $columns[] = $this->createColumn(ArrayUtils::get($column, 'field'), $column); + } + } + + return $columns; + } + + /** + * @param string $name + * @param array $data + * + * @return Column + */ + public function createColumn($name, array $data) + { + $this->validate($data); + $type = $this->schemaManager->getDataType(ArrayUtils::get($data, 'type')); + $autoincrement = ArrayUtils::get($data, 'auto_increment', false); + $unique = ArrayUtils::get($data, 'unique', false); + $primaryKey = ArrayUtils::get($data, 'primary_key', false); + $length = ArrayUtils::get($data, 'length', $this->schemaManager->getFieldDefaultLength($type)); + $nullable = ArrayUtils::get($data, 'nullable', true); + $default = ArrayUtils::get($data, 'default_value', null); + $unsigned = ArrayUtils::get($data, 'unsigned', false); + // TODO: Make comment work in an abstract level + // ZendDB doesn't support charset nor comment + // $comment = ArrayUtils::get($data, 'comment'); + + $column = $this->createColumnFromType($name, $type); + $column->setNullable($nullable); + $column->setDefault($default); + + if (!$autoincrement && $unique === true) { + $column->setOption('unique', $unique); + } + + if ($primaryKey === true) { + $column->setOption('primary_key', $primaryKey); + } + + // CollectionLength are SET or ENUM data type + if ($column instanceof AbstractLengthColumn || $column instanceof CollectionLength) { + $column->setLength($length); + } else { + $column->setOption('length', $length); + } + + // Only works for integers + if ($column instanceof Integer) { + $column->setOption('autoincrement', $autoincrement); + $column->setOption('unsigned', $unsigned); + } + + return $column; + } + + /** + * Creates the given table + * + * @param AbstractSql|AlterTable|CreateTable $table + * + * @return \Zend\Db\Adapter\Driver\StatementInterface|\Zend\Db\ResultSet\ResultSet + */ + public function buildTable(AbstractSql $table) + { + $connection = $this->schemaManager->getSource()->getConnection(); + $sql = new Sql($connection); + + // TODO: Allow charset and comment + return $connection->query( + $sql->buildSqlString($table), + $connection::QUERY_MODE_EXECUTE + ); + } + + /** + * Creates column based on type + * + * @param $name + * @param $type + * + * @return Column + * + * @throws UnknownDataTypeException + */ + protected function createColumnFromType($name, $type) + { + switch (strtolower($type)) { + case DataTypes::TYPE_CHAR: + $column = new Char($name); + break; + case DataTypes::TYPE_VARCHAR: + $column = new Varchar($name); + break; + case DataTypes::TYPE_TINY_JSON: + case DataTypes::TYPE_TINY_TEXT: + $column = new TinyText($name); + break; + case DataTypes::TYPE_JSON: + case DataTypes::TYPE_TEXT: + $column = new Text($name); + break; + case DataTypes::TYPE_MEDIUM_JSON: + case DataTypes::TYPE_MEDIUM_TEXT: + $column = new MediumText($name); + break; + case DataTypes::TYPE_LONG_JSON: + case DataTypes::TYPE_LONGTEXT: + $column = new LongText($name); + break; + case DataTypes::TYPE_UUID: + $column = new Uuid($name); + break; + case DataTypes::TYPE_CSV: + case DataTypes::TYPE_ARRAY: + $column = new Varchar($name); + break; + + case DataTypes::TYPE_TIME: + $column = new Time($name); + break; + case DataTypes::TYPE_DATE: + $column = new Date($name); + break; + case DataTypes::TYPE_DATETIME: + $column = new Datetime($name); + break; + case DataTypes::TYPE_TIMESTAMP: + $column = new Timestamp($name); + break; + + case DataTypes::TYPE_TINY_INT: + $column = new TinyInteger($name); + break; + case DataTypes::TYPE_SMALL_INT: + $column = new SmallInteger($name); + break; + case DataTypes::TYPE_INTEGER: + case DataTypes::TYPE_INT: + $column = new Integer($name); + break; + case DataTypes::TYPE_MEDIUM_INT: + $column = new MediumInteger($name); + break; + case DataTypes::TYPE_BIG_INT: + $column = new BigInteger($name); + break; + case DataTypes::TYPE_SERIAL: + $column = new Serial($name); + break; + case DataTypes::TYPE_FLOAT: + $column = new Floating($name); + break; + case DataTypes::TYPE_DOUBLE: + $column = new Double($name); + break; + case DataTypes::TYPE_DECIMAL: + $column = new Decimal($name); + break; + case DataTypes::TYPE_REAL: + $column = new Real($name); + break; + case DataTypes::TYPE_NUMERIC: + case DataTypes::TYPE_CURRENCY: + $column = new Numeric($name); + break; + case DataTypes::TYPE_BIT: + $column = new Bit($name); + break; + case DataTypes::TYPE_BOOL: + case DataTypes::TYPE_BOOLEAN: + $column = new Boolean($name); + break; + + case DataTypes::TYPE_BINARY: + $column = new Binary($name); + break; + case DataTypes::TYPE_VARBINARY: + $column = new Varbinary($name); + break; + case DataTypes::TYPE_TINY_BLOB: + $column = new TinyBlob($name); + break; + case DataTypes::TYPE_BLOB: + $column = new Blob($name); + break; + case DataTypes::TYPE_MEDIUM_BLOB: + $column = new MediumBlob($name); + break; + case DataTypes::TYPE_LONG_BLOB: + $column = new LongBlob($name); + break; + + case DataTypes::TYPE_SET: + $column = new Set($name); + break; + case DataTypes::TYPE_ENUM: + $column = new Enum($name); + break; + + case DataTypes::TYPE_FILE: + $column = new File($name); + break; + + default: + throw new UnknownDataTypeException($type); + break; + } + + return $column; + } + + /** + * @param array $columnData + * + * @throws InvalidRequestException + */ + protected function validate(array $columnData) + { + $constraints = [ + 'field' => ['required', 'string'], + 'type' => ['required', 'string'], + 'interface' => ['required', 'string'] + ]; + + // Copied from route + // TODO: Route needs a restructure to get the utils code like this shared + $violations = []; + $data = ArrayUtils::pick($columnData, array_keys($constraints)); + foreach (array_keys($constraints) as $field) { + $violations[$field] = $this->validator->validate(ArrayUtils::get($data, $field), $constraints[$field]); + } + + $messages = []; + /** @var ConstraintViolationList $violation */ + foreach ($violations as $field => $violation) { + $iterator = $violation->getIterator(); + + $errors = []; + while ($iterator->valid()) { + $constraintViolation = $iterator->current(); + $errors[] = $constraintViolation->getMessage(); + $iterator->next(); + } + + if ($errors) { + $messages[] = sprintf('%s: %s', $field, implode(', ', $errors)); + } + } + + if (count($messages) > 0) { + throw new InvalidRequestException(implode(' ', $messages)); + } + } +} diff --git a/src/core/Directus/Database/Schema/SchemaManager.php b/src/core/Directus/Database/Schema/SchemaManager.php new file mode 100644 index 0000000000..036dda91e1 --- /dev/null +++ b/src/core/Directus/Database/Schema/SchemaManager.php @@ -0,0 +1,653 @@ +source = $source; + } + + /** + * Adds a primary key to the given column + * + * @param $table + * @param $column + * + * @return bool + */ + public function addPrimaryKey($table, $column) + { + return $this->source->addPrimaryKey($table, $column); + } + + /** + * Removes the primary key of the given column + * + * @param $table + * @param $column + * + * @return bool + */ + public function dropPrimaryKey($table, $column) + { + return $this->source->dropPrimaryKey($table, $column); + } + + /** + * Get the table schema information + * + * @param string $tableName + * @param array $params + * @param bool $skipCache + * + * @throws CollectionNotFoundException + * + * @return \Directus\Database\Schema\Object\Collection + */ + public function getCollection($collectionName, $params = [], $skipCache = false) + { + $collection = ArrayUtils::get($this->data, 'collections.' . $collectionName, null); + if (!$collection || $skipCache) { + // Get the table schema data from the source + $collectionResult = $this->source->getCollection($collectionName); + $collectionData = $collectionResult->current(); + + if (!$collectionData) { + throw new CollectionNotFoundException($collectionName); + } + + // Create a table object based of the table schema data + $collection = $this->createCollectionFromArray(array_merge($collectionData, [ + 'schema' => $this->source->getSchemaName() + ])); + $this->addCollection($collectionName, $collection); + } + + // ============================================================================= + // Set table columns + // ----------------------------------------------------------------------------- + // @TODO: Do not allow to add duplicate column names + // ============================================================================= + if (empty($collection->getFields())) { + $fields = $this->getFields($collectionName); + $collection->setFields($fields); + } + + return $collection; + } + + /** + * Gets column schema + * + * @param $tableName + * @param $columnName + * @param bool $skipCache + * + * @return Field + */ + public function getField($tableName, $columnName, $skipCache = false) + { + $columnSchema = ArrayUtils::get($this->data, 'fields.' . $tableName . '.' . $columnName, null); + + if (!$columnSchema || $skipCache) { + // Get the column schema data from the source + $columnResult = $this->source->getFields($tableName, ['column_name' => $columnName]); + $columnData = $columnResult->current(); + + // Create a column object based of the table schema data + $columnSchema = $this->createFieldFromArray($columnData); + $this->addField($columnSchema); + } + + return $columnSchema; + } + + /** + * Add the system table prefix to to a table name. + * + * @param string|array $names + * + * @return array + */ + public function addSystemCollectionPrefix($names) + { + if (!is_array($names)) { + $names = [$names]; + } + + return array_map(function ($name) { + // TODO: Directus tables prefix _probably_ will be dynamic + return $this->prefix . $name; + }, $names); + } + + /** + * Get Directus System tables name + * + * @return array + */ + public function getSystemCollections() + { + return $this->addSystemCollectionPrefix($this->directusTables); + } + + /** + * Check if the given name is a system table + * + * @param $name + * + * @return bool + */ + public function isSystemCollection($name) + { + return in_array($name, $this->getSystemCollections()); + } + + /** + * Check if a table name exists + * + * @param $tableName + * @return bool + */ + public function tableExists($tableName) + { + return $this->source->collectionExists($tableName); + } + + /** + * Gets list of table + * + * @param array $params + * + * @return Collection[] + */ + public function getCollections(array $params = []) + { + // TODO: Filter should be outsite + // $schema = Bootstrap::get('schema'); + // $config = Bootstrap::get('config'); + + // $ignoredTables = static::getDirectusTables(DirectusPreferencesTableGateway::$IGNORED_TABLES); + // $blacklistedTable = $config['tableBlacklist']; + // array_merge($ignoredTables, $blacklistedTable) + $collections = $this->source->getCollections(); + + $tables = []; + foreach ($collections as $collection) { + // Create a table object based of the table schema data + $tableSchema = $this->createCollectionFromArray(array_merge($collection, [ + 'schema' => $this->source->getSchemaName() + ])); + $tableName = $tableSchema->getName(); + $this->addCollection($tableName, $tableSchema); + + $tables[$tableName] = $tableSchema; + } + + return $tables; + } + + /** + * Get all columns in the given table name + * + * @param $tableName + * @param array $params + * + * @return \Directus\Database\Schema\Object\Field[] + */ + public function getFields($tableName, $params = []) + { + // TODO: filter black listed fields on services level + + $columnsSchema = ArrayUtils::get($this->data, 'columns.' . $tableName, null); + if (!$columnsSchema) { + $columnsResult = $this->source->getFields($tableName, $params); + $relationsResult = $this->source->getRelations($tableName); + + // TODO: Improve this logic + $relationsA = []; + $relationsB = []; + foreach ($relationsResult as $relation) { + $relationsA[$relation['field_a']] = $relation; + + if (isset($relation['field_b'])) { + $relationsB[$relation['field_b']] = $relation; + } + } + + $columnsSchema = []; + foreach ($columnsResult as $column) { + $field = $this->createFieldFromArray($column); + + // Set all FILE data type related to directus files (M2O) + if (DataTypes::isFilesType($field->getType())) { + $field->setRelationship([ + 'collection_a' => $field->getCollectionName(), + 'field_a' => $field->getName(), + 'collection_b' => static::COLLECTION_FILES, + 'field_b' => 'id' + ]); + } else if (array_key_exists($field->getName(), $relationsA)) { + $field->setRelationship($relationsA[$field->getName()]); + } else if (array_key_exists($field->getName(), $relationsB)) { + $field->setRelationship($relationsB[$field->getName()]); + } + + $columnsSchema[] = $field; + } + + $this->data['columns'][$tableName] = $columnsSchema; + } + + return $columnsSchema; + } + + public function getFieldsName($tableName) + { + $columns = $this->getFields($tableName); + + $columnNames = []; + foreach ($columns as $column) { + $columnNames[] = $column->getName(); + } + + return $columnNames; + } + + /** + * Get all the columns + * + * @return Field[] + */ + public function getAllFields() + { + $allColumns = $this->source->getAllFields(); + + $columns = []; + foreach($allColumns as $column) { + $columns[] = $this->createFieldFromArray($column); + } + + return $columns; + } + + /** + * Get a list of columns table grouped by table name + * + * @return array + */ + public function getAllFieldsByCollection() + { + $fields = []; + foreach ($this->getAllFields() as $field) { + $collectionName = $field->getCollectionName(); + if (!isset($fields[$collectionName])) { + $fields[$collectionName] = []; + } + + $columns[$collectionName][] = $field; + } + + return $fields; + } + + public function getPrimaryKey($tableName) + { + $collection = $this->getCollection($tableName); + if ($collection) { + return $collection->getPrimaryKeyName(); + } + + return false; + } + + public function hasSystemDateField($tableName) + { + $tableObject = $this->getCollection($tableName); + + return $tableObject->getDateCreatedField() || $tableObject->getDateModifiedField(); + } + + public function castRecordValues($records, $columns) + { + return $this->source->castRecordValues($records, $columns); + } + + /** + * Cast value against a database type + * + * NOTE: it only works with MySQL data types + * + * @param $value + * @param $type + * @param $length + * + * @return mixed + */ + public function castValue($value, $type = null, $length = false) + { + return $this->source->castValue($value, $type, $length); + } + + /** + * Checks whether the given type is numeric type + * + * @param $type + * + * @return bool + */ + public function isNumericType($type) + { + return DataTypes::isNumericType($type); + } + + /** + * Checks whether the given type is string type + * + * @param $type + * + * @return bool + */ + public function isStringType($type) + { + return DataTypes::isStringType($type); + } + + /** + * Checks whether the given type is integer type + * + * @param $type + * + * @return bool + */ + public function isIntegerType($type) + { + return DataTypes::isIntegerType($type); + } + + /** + * Checks whether the given type is decimal type + * + * @param $type + * + * @return bool + */ + public function isFloatingPointType($type) + { + return static::isFloatingPointType($type); + } + + /** + * Cast default value + * + * @param $value + * @param $type + * @param $length + * + * @return mixed + */ + public function castDefaultValue($value, $type, $length = null) + { + if (strtolower($value) === 'null') { + $value = null; + } else { + $value = $this->castValue($value, $type, $length); + } + + return $value; + } + + /** + * Get all Directus system tables name + * + * @param array $filterNames + * + * @return array + */ + public function getDirectusCollections(array $filterNames = []) + { + $tables = $this->directusTables; + if ($filterNames) { + foreach ($tables as $i => $table) { + if (!in_array($table, $filterNames)) { + unset($tables[$i]); + } + } + } + + return $this->addSystemCollectionPrefix($tables); + } + + /** + * Check if a given table is a directus system table name + * + * @param $tableName + * + * @return bool + */ + public function isDirectusCollection($tableName) + { + return in_array($tableName, $this->getDirectusCollections()); + } + + /** + * Get the schema adapter + * + * @return SchemaInterface + */ + public function getSchema() + { + return $this->source; + } + + /** + * List of supported databases + * + * @return array + */ + public static function getSupportedDatabases() + { + return [ + 'mysql' => [ + 'id' => 'mysql', + 'name' => 'MySQL/Percona' + ], + ]; + } + + public static function getTemplates() + { + // @TODO: SchemaManager shouldn't be a class with static methods anymore + // the UI templates list will be provided by a container or bootstrap. + $path = implode(DIRECTORY_SEPARATOR, [ + base_path(), + 'api', + 'migrations', + 'templates', + '*' + ]); + + $templatesDirs = glob($path, GLOB_ONLYDIR); + $templatesData = []; + foreach ($templatesDirs as $dir) { + $key = basename($dir); + $templatesData[$key] = [ + 'id' => $key, + 'name' => uc_convert($key) + ]; + } + + return $templatesData; + } + + /** + * Gets a collection object from an array attributes data + * @param $data + * + * @return Collection + */ + public function createCollectionFromArray($data) + { + return new Collection($data); + } + + /** + * Creates a column object from the given array + * + * @param array $column + * + * @return Field + */ + public function createFieldFromArray($column) + { + // PRIMARY KEY must be required + if ($column['key'] === 'PRI') { + $column['required'] = true; + } + + $options = json_decode(isset($column['options']) ? $column['options'] : '', true); + $column['options'] = $options ? $options : null; + + // NOTE: Alias column must are nullable + if (strtoupper($column['type']) === 'ALIAS') { + $column['nullable'] = 1; + } + + // NOTE: MariaDB store "NULL" as a string on some data types such as VARCHAR. + // We reserved the word "NULL" on nullable data type to be actually null + if ($column['nullable'] === 1 && $column['default_value'] == 'NULL') { + $column['default_value'] = null; + } + + return new Field($column); + } + + /** + * Checks whether the given type is a unique type + * + * @param $type + * + * @return bool + */ + public function isUniqueFieldType($type) + { + return DataTypes::isUniqueType($type); + } + + protected function addCollection($name, $schema) + { + // save the column into the data + // @NOTE: this is the early implementation of cache + // soon this will be change to cache + $this->data['tables'][$name] = $schema; + } + + protected function addField(Field $column) + { + $tableName = $column->getCollectionName(); + $columnName = $column->getName(); + $this->data['fields'][$tableName][$columnName] = $column; + } + + /** + * + * + * @param $type + * + * @return integer + */ + public function getFieldDefaultLength($type) + { + return $this->source->getColumnDefaultLength($type); + } + + /** + * Gets the column type based the schema adapter + * + * @param string $type + * + * @return string + */ + public function getDataType($type) + { + return $this->source->getDataType($type); + } + + /** + * Gets the source schema adapter + * + * @return SchemaInterface + */ + public function getSource() + { + return $this->source; + } +} diff --git a/src/core/Directus/Database/Schema/Sources/AbstractSchema.php b/src/core/Directus/Database/Schema/Sources/AbstractSchema.php new file mode 100644 index 0000000000..78711c9f23 --- /dev/null +++ b/src/core/Directus/Database/Schema/Sources/AbstractSchema.php @@ -0,0 +1,137 @@ + $record) { + $fieldName = $field->getName(); + if (ArrayUtils::has($record, $fieldName)) { + $records[$index][$fieldName] = $this->castValue($record[$fieldName], $field->getType()); + } + } + } + + return $singleRecord ? reset($records) : $records; + } + + /** + * Parse records value by its column data type + * + * @see AbastractSchema::castRecordValues + * + * @param array $records + * @param $columns + * + * @return array + */ + public function parseRecordValuesByType(array $records, $columns) + { + return $this->castRecordValues($records, $columns); + } + + /** + * @inheritdoc + */ + public function getDefaultLengths() + { + return [ + // 'ALIAS' => static::INTERFACE_ALIAS, + // 'MANYTOMANY' => static::INTERFACE_ALIAS, + // 'ONETOMANY' => static::INTERFACE_ALIAS, + + // 'BIT' => static::INTERFACE_TOGGLE, + // 'TINYINT' => static::INTERFACE_TOGGLE, + + // 'MEDIUMBLOB' => static::INTERFACE_BLOB, + // 'BLOB' => static::INTERFACE_BLOB, + + // 'TINYTEXT' => static::INTERFACE_TEXT_AREA, + // 'TEXT' => static::INTERFACE_TEXT_AREA, + // 'MEDIUMTEXT' => static::INTERFACE_TEXT_AREA, + // 'LONGTEXT' => static::INTERFACE_TEXT_AREA, + + 'CHAR' => 1, + 'VARCHAR' => 255, + // 'POINT' => static::INTERFACE_TEXT_INPUT, + + // 'DATETIME' => static::INTERFACE_DATETIME, + // 'TIMESTAMP' => static::INTERFACE_DATETIME, + + // 'DATE' => static::INTERFACE_DATE, + + // 'TIME' => static::INTERFACE_TIME, + + // 'YEAR' => static::INTERFACE_NUMERIC, + // 'SMALLINT' => static::INTERFACE_NUMERIC, + // 'MEDIUMINT' => static::INTERFACE_NUMERIC, + 'INT' => 11, + 'INTEGER' => 11, + // 'BIGINT' => static::INTERFACE_NUMERIC, + // 'FLOAT' => static::INTERFACE_NUMERIC, + // 'DOUBLE' => static::INTERFACE_NUMERIC, + // 'DECIMAL' => static::INTERFACE_NUMERIC, + ]; + } + + /** + * @inheritdoc + */ + public function getColumnDefaultLength($type) + { + return ArrayUtils::get($this->getDefaultLengths(), strtoupper($type), null); + } + + /** + * @inheritdoc + */ + public function isType($type, array $list) + { + return in_array(strtolower($type), $list); + } + + /** + * @inheritdoc + */ + public function getDataType($type) + { + switch (strtolower($type)) { + case 'array': + case 'json': + $type = 'text'; + break; + case 'tinyjson': + $type = 'tinytext'; + break; + case 'mediumjson': + $type = 'mediumtext'; + break; + case 'longjson': + $type = 'longtext'; + break; + } + + return $type; + } +} diff --git a/src/core/Directus/Database/Schema/Sources/MySQLSchema.php b/src/core/Directus/Database/Schema/Sources/MySQLSchema.php new file mode 100644 index 0000000000..710e3174a9 --- /dev/null +++ b/src/core/Directus/Database/Schema/Sources/MySQLSchema.php @@ -0,0 +1,599 @@ +adapter = $adapter; + } + + /** + * Get the schema name + * + * @return string + */ + public function getSchemaName() + { + return $this->adapter->getCurrentSchema(); + } + + /** + * @return \Zend\DB\Adapter\Adapter + */ + public function getConnection() + { + return $this->adapter; + } + + /** + * @inheritDoc + */ + public function getCollections(array $params = []) + { + $select = new Select(); + $select->columns([ + 'collection' => 'TABLE_NAME', + 'date_created' => 'CREATE_TIME', + 'collation' => 'TABLE_COLLATION', + 'schema_comment' => 'TABLE_COMMENT' + ]); + $select->from(['ST' => new TableIdentifier('TABLES', 'INFORMATION_SCHEMA')]); + $select->join( + ['DT' => 'directus_collections'], + 'DT.collection = ST.TABLE_NAME', + [ + 'note', + 'hidden' => new Expression('IFNULL(`DT`.`hidden`, 0)'), + 'single' => new Expression('IFNULL(`DT`.`single`, 0)'), + 'item_name_template', + 'preview_url', + 'managed' => new Expression('IF(ISNULL(`DT`.`collection`), 0, 1)') + ], + $select::JOIN_LEFT + ); + + $condition = [ + 'ST.TABLE_SCHEMA' => $this->adapter->getCurrentSchema(), + 'ST.TABLE_TYPE' => 'BASE TABLE' + ]; + + $select->where($condition); + if (isset($params['name'])) { + $tableName = $params['name']; + // hotfix: This solve the problem fetching a table with capital letter + $where = $select->where->nest(); + $where->equalTo('ST.TABLE_NAME', $tableName); + $where->OR; + $where->equalTo('ST.TABLE_NAME', $tableName); + $where->unnest(); + } + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + return $result; + } + + /** + * @inheritDoc + */ + public function collectionExists($collectionsName) + { + if (is_string($collectionsName)) { + $collectionsName = [$collectionsName]; + } + + $select = new Select(); + $select->columns(['TABLE_NAME']); + $select->from(['T' => new TableIdentifier('TABLES', 'INFORMATION_SCHEMA')]); + $select->where([ + new In('T.TABLE_NAME', $collectionsName), + 'T.TABLE_SCHEMA' => $this->adapter->getCurrentSchema() + ]); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + return $result->count() ? true : false; + } + + /** + * @inheritDoc + */ + public function getCollection($collectionName) + { + return $this->getCollections(['name' => $collectionName]); + } + + /** + * @inheritDoc + */ + public function getFields($tableName, $params = null) + { + return $this->getAllFields(['collection' => $tableName]); + } + + /** + * @inheritDoc + */ + public function getAllFields(array $params = []) + { + $selectOne = new Select(); + // $selectOne->quantifier($selectOne::QUANTIFIER_DISTINCT); + $selectOne->columns([ + 'collection' => 'TABLE_NAME', + 'field' => 'COLUMN_NAME', + 'sort' => new Expression('IFNULL(DF.sort, SF.ORDINAL_POSITION)'), + 'original_type' => new Expression('UCASE(SF.DATA_TYPE)'), + 'key' => 'COLUMN_KEY', + 'extra' => 'EXTRA', + 'char_length' => 'CHARACTER_MAXIMUM_LENGTH', + 'precision' => 'NUMERIC_PRECISION', + 'scale' => 'NUMERIC_SCALE', + 'nullable' => new Expression('IF(SF.IS_NULLABLE="YES",1,0)'), + 'default_value' => 'COLUMN_DEFAULT', + 'note' => new Expression('IFNULL(DF.note, SF.COLUMN_COMMENT)'), + 'column_type' => 'COLUMN_TYPE', + ]); + + $selectOne->from(['SF' => new TableIdentifier('COLUMNS', 'INFORMATION_SCHEMA')]); + $selectOne->join( + ['DF' => 'directus_fields'], + 'SF.COLUMN_NAME = DF.field AND SF.TABLE_NAME = DF.collection', + [ + 'id' => new Expression('IF(ISNULL(DF.id), NULL, DF.id)'), + 'type' => new Expression('UCASE(IFNULL(DF.type, SF.DATA_TYPE))'), + 'managed' => new Expression('IF(ISNULL(DF.id),0,1)'), + 'interface', + 'hidden_input' => new Expression('IF(DF.hidden_input=1,1,0)'), + 'hidden_list' => new Expression('IF(DF.hidden_list=1,1,0)'), + 'required' => new Expression('IF(DF.required=1,1,0)'), + 'options', + 'locked', + 'translation', + 'readonly', + 'view_width', + 'validation', + 'group', + 'view_width', + ], + $selectOne::JOIN_LEFT + ); + + $selectOne->where([ + 'SF.TABLE_SCHEMA' => $this->adapter->getCurrentSchema(), + // 'T.TABLE_TYPE' => 'BASE TABLE' + ]); + + if (isset($params['collection'])) { + $selectOne->where([ + 'SF.TABLE_NAME' => $params['collection'] + ]); + } + + $selectTwo = new Select(); + $selectTwo->columns([ + 'collection', + 'field', + 'sort', + 'original_type' => new Expression('NULL'), + 'key' => new Expression('NULL'), + 'extra' => new Expression('NULL'), + 'char_length' => new Expression('NULL'), + 'precision' => new Expression('NULL'), + 'scale' => new Expression('NULL'), + 'is_nullable' => new Expression('"NO"'), + 'default_value' => new Expression('NULL'), + 'note', + 'column_type' => new Expression('NULL'), + 'id', + 'type' => new Expression('UCASE(type)'), + 'managed' => new Expression('IF(ISNULL(DF2.id),0,1)'), + 'interface', + 'hidden_input', + 'hidden_list', + 'required', + 'options', + 'locked', + 'translation', + 'readonly', + 'view_width', + 'validation', + 'group', + 'view_width', + ]); + $selectTwo->from(['DF2' => 'directus_fields']); + + $where = new Where(); + $where->addPredicate(new In(new Expression('UCASE(type)'), DataTypes::getAliasTypes())); + if (isset($params['collection'])) { + $where->equalTo('DF2.collection', $params['collection']); + } + + $selectTwo->where($where); + + $selectOne->combine($selectTwo);//, $selectOne::COMBINE_UNION, 'ALL'); + $selectOne->order('collection'); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($selectOne); + $result = $statement->execute(); + + return $result; + } + + /** + * @inheritDoc + */ + public function hasField($tableName, $columnName) + { + // TODO: Implement hasColumn() method. + } + + /** + * @inheritDoc + */ + public function getField($tableName, $columnName) + { + return $this->getFields($tableName, ['field' => $columnName])->current(); + } + + /** + * @inheritdoc + */ + public function getAllRelations() + { + // TODO: Implement getAllRelations() method. + } + + public function getRelations($collectionName) + { + $selectOne = new Select(); + // $selectOne->quantifier($selectOne::QUANTIFIER_DISTINCT); + $selectOne->columns([ + 'id', + 'collection_a', + 'field_a', + 'junction_key_a', + 'junction_collection', + 'junction_mixed_collections', + 'junction_key_b', + 'collection_b', + 'field_b' + ]); + + $selectOne->from('directus_relations'); + + $where = $selectOne->where->nest(); + $where->equalTo('collection_a', $collectionName); + $where->OR; + $where->equalTo('collection_b', $collectionName); + $where->unnest(); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($selectOne); + $result = $statement->execute(); + + return $result; + } + + /** + * @inheritDoc + */ + public function hasPrimaryKey($tableName) + { + // TODO: Implement hasPrimaryKey() method. + } + + /** + * @inheritDoc + */ + public function getPrimaryKey($tableName) + { + $select = new Select(); + $columnName = null; + + // @todo: make this part of loadSchema + // without the need to use acl and create a infinite nested function call + $select->columns([ + 'column_name' => 'COLUMN_NAME' + ]); + $select->from(new TableIdentifier('COLUMNS', 'INFORMATION_SCHEMA')); + $select->where([ + 'TABLE_NAME' => $tableName, + 'TABLE_SCHEMA' => $this->adapter->getCurrentSchema(), + 'COLUMN_KEY' => 'PRI' + ]); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + // @TODO: Primary key can be more than one. + $column = $result->current(); + if ($column) { + $columnName = $column['column_name']; + } + + return $columnName; + } + + /** + * @inheritDoc + */ + public function getFullSchema() + { + // TODO: Implement getFullSchema() method. + } + + /** + * @inheritDoc + */ + public function getColumnUI($column) + { + // TODO: Implement getColumnUI() method. + } + + /** + * Add primary key to an existing column + * + * @param $table + * @param $column + * + * @return \Zend\Db\Adapter\Driver\StatementInterface|\Zend\Db\ResultSet\ResultSet + * + * @throws Exception + */ + public function addPrimaryKey($table, $column) + { + $columnData = $this->getField($table, $column); + + if (!$columnData) { + // TODO: Better error message + throw new Exception('Missing column'); + } + + $dataType = ArrayUtils::get($columnData, 'type'); + + if (!$dataType) { + // TODO: Better error message + throw new Exception('Missing data type'); + } + + $queryFormat = 'ALTER TABLE `%s` ADD PRIMARY KEY(`%s`)'; + // NOTE: Make this work with strings + if ($this->isNumericType($dataType)) { + $queryFormat .= ', MODIFY COLUMN `%s` %s AUTO_INCREMENT'; + } + + $query = sprintf($queryFormat, $table, $column, $column, $dataType); + $connection = $this->adapter; + + return $connection->query($query, $connection::QUERY_MODE_EXECUTE); + } + + /** + * @inheritDoc + */ + public function dropPrimaryKey($table, $column) + { + $columnData = $this->getField($table, $column); + + if (!$columnData) { + // TODO: Better message + throw new Exception('Missing column'); + } + + $dataType = ArrayUtils::get($columnData, 'type'); + + if (!$dataType) { + // TODO: Better message + throw new Exception('Missing data type'); + } + + $queryFormat = 'ALTER TABLE `%s` CHANGE COLUMN `%s` `%s` %s NOT NULL, DROP PRIMARY KEY'; + $query = sprintf($queryFormat, $table, $column, $column, $dataType); + $connection = $this->adapter; + + return $connection->query($query, $connection::QUERY_MODE_EXECUTE); + } + + /** + * Cast string values to its database type. + * + * @param $data + * @param $type + * @param $length + * + * @return mixed + */ + public function castValue($data, $type = null, $length = false) + { + $type = strtolower($type); + + switch ($type) { + case 'bool': + case 'boolean': + $data = boolval($data); + break; + case 'tinyjson': + case 'json': + case 'mediumjson': + case 'longjson': + if ($data) { + $data = is_string($data) ? json_decode($data) : $data; + } else { + $data = null; + } + break; + case 'blob': + case 'mediumblob': + // NOTE: Do we really need to encode the blob? + $data = base64_encode($data); + break; + case 'year': + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'integer': + case 'bigint': + case 'serial': + $data = ($data === null) ? null : (int)$data; + break; + case 'numeric': + case 'float': + case 'real': + case 'decimal': + case 'double': + $data = (float)$data; + break; + case 'date': + case 'datetime': + $format = 'Y-m-d'; + $zeroData = '0000-00-00'; + if ($type === 'datetime') { + $format .= ' H:i:s'; + $zeroData .= ' 00:00:00'; + } + + if ($data === $zeroData) { + $data = null; + } + $datetime = \DateTime::createFromFormat($format, $data); + $data = $datetime ? $datetime->format($format) : null; + break; + case 'time': + // NOTE: Assuming this are all valid formatted data + $data = !empty($data) ? $data : null; + break; + case 'char': + case 'varchar': + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'var_string': + break; + } + + return $data; + } + + public function parseType($data, $type = null, $length = false) + { + return $this->castValue($data, $type, $length); + } + + /** + * @inheritdoc + */ + public function getDecimalTypes() + { + return [ + 'double', + 'decimal', + 'float' + ]; + } + + /** + * @inheritdoc + */ + public function getIntegerTypes() + { + return [ + 'year', + 'bigint', + 'smallint', + 'mediumint', + 'int', + 'long', + 'tinyint' + ]; + } + + /** + * @inheritdoc + */ + public function getNumericTypes() + { + return array_merge($this->getDecimalTypes(), $this->getIntegerTypes()); + } + + /** + * @inheritdoc + */ + public function isDecimalType($type) + { + return $this->isType($type, $this->getDecimalTypes()); + } + + /** + * @inheritdoc + */ + public function isIntegerType($type) + { + return $this->isType($type, $this->getIntegerTypes()); + } + + /** + * @inheritdoc + */ + public function isNumericType($type) + { + return in_array(strtolower($type), $this->getNumericTypes()); + } + + /** + * @inheritdoc + */ + public function getStringTypes() + { + return [ + 'char', + 'varchar', + 'text', + 'enum', + 'set', + 'tinytext', + 'text', + 'mediumtext', + 'longtext' + ]; + } + + /** + * @inheritdoc + */ + public function isStringType($type) + { + return in_array(strtolower($type), $this->getStringTypes()); + } +} diff --git a/src/core/Directus/Database/Schema/Sources/SQLiteSchema.php b/src/core/Directus/Database/Schema/Sources/SQLiteSchema.php new file mode 100644 index 0000000000..1f2a2521c9 --- /dev/null +++ b/src/core/Directus/Database/Schema/Sources/SQLiteSchema.php @@ -0,0 +1,410 @@ +metadata = new SqliteMetadata($adapter); + } + + /** + * @inheritDoc + */ + public function getTables() + { + $tablesObject = $this->metadata->getTables(); + $directusTablesInfo = $this->getDirectusTablesInfo(); + + return $this->formatTablesFromInfo($tablesObject, $directusTablesInfo); + } + + protected function getDirectusTablesInfo() + { + $config = Bootstrap::get('config'); + + $blacklist = []; + if ($config->has('tableBlacklist')) { + $blacklist = $config->get('tableBlacklist'); + } + + $select = new Select(); + $select->columns([ + 'table_name', + 'hidden' => new Expression('IFNULL(hidden, 0)'), + 'single' => new Expression('IFNULL(single, 0)'), + 'user_create_column', + 'user_update_column', + 'date_create_column', + 'date_update_column', + 'footer', + 'list_view', + 'column_groupings', + 'filter_column_blacklist', + 'primary_column' + ]); + $select->from('directus_tables'); + + $skipTables = array_merge(SchemaManager::getDirectusTables(), (array)$blacklist); + $select->where([ + new NotIn('table_name', $skipTables), + ]); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + return iterator_to_array($result); + } + + protected function formatTablesFromInfo($tablesObject, $directusTablesInfo) + { + $tables = []; + foreach ($tablesObject as $tableObject) { + $directusTableInfo = []; + foreach ($directusTablesInfo as $index => $table) { + if ($table['table_name'] == $tableObject->getName()) { + $directusTableInfo = $table; + unset($directusTablesInfo[$index]); + } + } + + $tables[] = $this->formatTableFromInfo($tableObject, $directusTableInfo); + } + + return $tables; + } + + protected function formatTableFromInfo($tableObject, $directusTableInfo) + { + return [ + 'id' => $tableObject->getName(), + 'table_name' => $tableObject->getName(), + 'date_created' => null, + 'comment' => '', + 'count' => null, + 'hidden' => ArrayUtils::get($directusTableInfo, 'hidden', 0), + 'single' => ArrayUtils::get($directusTableInfo, 'single', 0), + 'user_create_column' => ArrayUtils::get($directusTableInfo, 'user_create_column', null), + 'user_update_column' => ArrayUtils::get($directusTableInfo, 'user_update_column', null), + 'date_create_column' => ArrayUtils::get($directusTableInfo, 'date_create_column', null), + 'date_update_column' => ArrayUtils::get($directusTableInfo, 'date_update_column', null), + 'footer' => ArrayUtils::get($directusTableInfo, 'footer', 0), + 'list_view' => ArrayUtils::get($directusTableInfo, 'list_view', null), + 'column_groupings' => ArrayUtils::get($directusTableInfo, 'column_groupings', null), + 'filter_column_blacklist' => ArrayUtils::get($directusTableInfo, 'filter_column_blacklist', null), + 'primary_column' => ArrayUtils::get($directusTableInfo, 'primary_column', null) + ]; + } + + /** + * @inheritDoc + */ + public function hasTable($tableName) + { + // TODO: Implement hasTable() method. + } + + /** + * @inheritDoc + */ + public function tableExists($tableName) + { + return $this->hasTable($tableName); + } + + /** + * @inheritDoc + */ + public function someTableExists(array $tablesName) + { + return false; + } + + /** + * @inheritDoc + */ + public function getTable($tableName) + { + $tablesObject = $this->metadata->getTable($tableName); + $directusTablesInfo = $this->getDirectusTableInfo($tableName); + if (!$directusTablesInfo) { + $directusTablesInfo = []; + } + + return $this->formatTableFromInfo($tablesObject, $directusTablesInfo); + } + + public function getDirectusTableInfo($tableName) + { + $select = new Select(); + $select->columns([ + 'table_name', + 'hidden' => new Expression('IFNULL(hidden, 0)'), + 'single' => new Expression('IFNULL(single, 0)'), + 'user_create_column', + 'user_update_column', + 'date_create_column', + 'date_update_column', + 'footer', + 'list_view', + 'column_groupings', + 'filter_column_blacklist', + 'primary_column' + ]); + $select->from('directus_tables'); + + $select->where([ + 'table_name' => $tableName + ]); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + return $result->current(); + } + + /** + * @inheritDoc + */ + public function getColumns($tableName, $params = null) + { + $columnsInfo = $this->metadata->getColumns($tableName); + // OLD FILTER + // @TODO this should be a job for the SchemaManager + $columnName = isset($params['column_name']) ? $params['column_name'] : -1; + if ($columnName != -1) { + foreach ($columnsInfo as $index => $column) { + if ($column->getName() == $columnName) { + unset($columnsInfo[$index]); + break; + } + } + } + + $directusColumns = $this->getDirectusColumnsInfo($tableName, $params); + $columns = $this->formatColumnsFromInfo($columnsInfo, $directusColumns); + + return $columns; + } + + public function getAllColumns() + { + $allColumns = []; + $allTables = $this->getTables(); + + foreach ($allTables as $table) { + $columns = $this->getColumns($table['table_name']); + foreach ($columns as $index => $column) { + $columns[$index]['table_name'] = $table['table_name']; + } + + $allColumns = array_merge($allColumns, $columns); + } + + return $allColumns; + } + + protected function formatColumnsFromInfo($columnsInfo, $directusColumnsInfo) + { + $columns = []; + + foreach ($columnsInfo as $columnInfo) { + $directusColumnInfo = []; + foreach ($directusColumnsInfo as $index => $column) { + if ($column['column_name'] == $columnInfo->getName()) { + $directusColumnInfo = $column; + unset($directusColumnsInfo[$index]); + } + } + + $columns[] = $this->formatColumnFromInfo($columnInfo, $directusColumnInfo); + } + + return $columns; + } + + protected function formatColumnFromInfo($columnInfo, $directusColumnInfo) + { + $matches = []; + preg_match('#^([a-zA-Z]+)(\(.*\)){0,1}$#', $columnInfo->getDataType(), $matches); + + $dataType = strtoupper($matches[1]); + + return [ + 'id' => $columnInfo->getName(), + 'column_name' => $columnInfo->getName(), + 'type' => $dataType, + 'char_length' => $columnInfo->getCharacterMaximumLength(), + 'is_nullable' => $columnInfo->getIsNullable() ? 'YES' : 'NO', + 'default_value' => $columnInfo->getColumnDefault() == 'NULL' ? NULL : $columnInfo->getColumnDefault(), + 'comment' => '', + 'sort' => $columnInfo->getOrdinalPosition(), + 'column_type' => $columnInfo->getDataType(), + 'ui' => ArrayUtils::get($directusColumnInfo, 'ui', null), + 'hidden_input' => ArrayUtils::get($directusColumnInfo, 'hidden_input', 0), + 'relationship_type' => ArrayUtils::get($directusColumnInfo, 'relationship_type', null), + 'related_table' => ArrayUtils::get($directusColumnInfo, 'related_table', null), + 'junction_table' => ArrayUtils::get($directusColumnInfo, 'junction_table', null), + 'junction_key_left' => ArrayUtils::get($directusColumnInfo, 'junction_key_left', null), + 'junction_key_right' => ArrayUtils::get($directusColumnInfo, 'junction_key_right', null), + 'required' => ArrayUtils::get($directusColumnInfo, 'required', 0), + ]; + } + + /** + * Get all the columns information stored on Directus Columns table + * + * @param $tableName + * @param $params + * + * @return array + */ + protected function getDirectusColumnsInfo($tableName, $params = null) + { + $acl = Bootstrap::get('acl'); + + $blacklist = $readFieldBlacklist = $acl->getTablePrivilegeList($tableName, $acl::FIELD_READ_BLACKLIST); + $columnName = isset($params['column_name']) ? $params['column_name'] : -1; + + $select = new Select(); + $select->columns([ + 'id' => 'column_name', + 'column_name', + 'type' => new Expression('upper(data_type)'), + 'char_length' => new Expression('NULL'), + 'is_nullable' => new Expression('"NO"'), + 'default_value' => new Expression('NULL'), + 'comment', + 'sort', + 'column_type' => new Expression('NULL'), + 'ui', + 'hidden_input', + 'relationship_type', + 'related_table', + 'junction_table', + 'junction_key_left', + 'junction_key_right', + 'required' => new Expression('IFNULL(required, 0)') + ]); + $select->from('directus_columns'); + $where = new Where(); + $where + ->equalTo('TABLE_NAME', $tableName) + ->addPredicate(new In('data_type', ['alias', 'MANYTOMANY', 'ONETOMANY'])); + // ->nest() + // ->addPredicate(new \Zend\Db\Sql\Predicate\Expression("'$columnName' = '-1'")) + // ->OR + // ->equalTo('column_name', $columnName) + // ->unnest() + // ->addPredicate(new IsNotNull('data_type')); + + if ($columnName != -1) { + $where->equalTo('column_name', $columnName); + } + + if (count($blacklist)) { + $where->addPredicate(new NotIn('COLUMN_NAME', $blacklist)); + } + + $select->where($where); + $select->order('sort'); + + $sql = new Sql($this->adapter); + $statement = $sql->prepareStatementForSqlObject($select); + // $query = $sql->getSqlStringForSqlObject($select, $this->adapter->getPlatform()); + // echo $query; + $result = $statement->execute(); + $columns = iterator_to_array($result); + + return $columns; + } + + /** + * @inheritDoc + */ + public function hasColumn($tableName, $columnName) + { + // TODO: Implement hasColumn() method. + } + + /** + * @inheritDoc + */ + public function getColumn($tableName, $columnName) + { + // TODO: Implement getColumn() method. + } + + /** + * @inheritDoc + */ + public function hasPrimaryKey($tableName) + { + // TODO: Implement hasPrimaryKey() method. + } + + /** + * @inheritDoc + */ + public function getPrimaryKey($tableName) + { + $columnName = null; + + $constraints = $this->metadata->getConstraints($tableName); + foreach ($constraints as $constraint) { + if ($constraint->isPrimaryKey()) { + // @TODO: Directus should handle multiple columns + $columns = $constraint->getColumns(); + $columnName = array_shift($columns); + break; + } + } + + return $columnName; + } + + /** + * @inheritDoc + */ + public function getFullSchema() + { + // TODO: Implement getFullSchema() method. + } + + /** + * @inheritDoc + */ + public function getColumnUI($column) + { + // TODO: Implement getColumnUI() method. + } + + public function parseType($data, $type = null) + { + return $data; + } +} diff --git a/src/core/Directus/Database/Schema/Sources/SchemaInterface.php b/src/core/Directus/Database/Schema/Sources/SchemaInterface.php new file mode 100644 index 0000000000..6569603d58 --- /dev/null +++ b/src/core/Directus/Database/Schema/Sources/SchemaInterface.php @@ -0,0 +1,274 @@ +fromContainer('schema_manager') + ); + } + + return static::$schemaManager; + } + + /** + * Set the Schema Manager instance + * + * @param $schemaManager + */ + public static function setSchemaManagerInstance($schemaManager) + { + static::$schemaManager = $schemaManager; + } + + /** + * Get ACL Instance + * + * @return \Directus\Permissions\Acl + */ + public static function getAclInstance() + { + if (static::$acl === null) { + static::setAclInstance( + Application::getInstance()->fromContainer('acl') + ); + } + + return static::$acl; + } + + + /** + * Set ACL Instance + * @param $acl + */ + public static function setAclInstance($acl) + { + static::$acl = $acl; + } + + /** + * Get Connection Instance + * + * @return \Directus\Database\Connection + */ + public static function getConnectionInstance() + { + if (static::$connection === null) { + static::setConnectionInstance( + Application::getInstance()->fromContainer('schema_manager') + ); + } + + return static::$connection; + } + + public static function setConnectionInstance($connection) + { + static::$connection = $connection; + } + + public static function setConfig($config) + { + static::$config = $config; + } + + /** + * Gets table schema object + * + * @param $tableName + * @param array $params + * @param bool $skipCache + * @param bool $skipAcl + * + * @return Collection + */ + public static function getCollection($tableName, array $params = [], $skipCache = false, $skipAcl = false) + { + // if (!$skipAcl) { + // static::getAclInstance()->enforceRead($tableName); + // } + + return static::getSchemaManagerInstance()->getCollection($tableName, $params, $skipCache); + } + + public static function getCollectionOwnerField($collection) + { + $collectionObject = static::getCollection($collection); + + return $collectionObject->getUserCreatedField(); + } + + public static function getCollectionOwnerFieldName($collection) + { + $field = static::getCollectionOwnerField($collection); + + return $field ? $field->getName() : null; + } + + /** + * Gets table columns schema + * + * @param string $tableName + * @param array $params + * @param bool $skipCache + * + * @return Field[] + */ + public static function getFields($tableName, array $params = [], $skipCache = false) + { + $tableObject = static::getCollection($tableName, $params, $skipCache); + + return array_values(array_filter($tableObject->getFields(), function (Field $column) { + return static::canReadField($column->getCollectionName(), $column->getName()); + })); + } + + /** + * Gets the column object + * + * @param string $tableName + * @param string $columnName + * @param bool $skipCache + * @param bool $skipAcl + * + * @return Field + */ + public static function getField($tableName, $columnName, $skipCache = false, $skipAcl = false) + { + // Due to a problem the way we use to cache using array + // if a column information is fetched before its table + // the table is going to be created with only one column + // to prevent this we always get the table even if we only want one column + // Stop using getColumnSchema($tableName, $columnName); until we fix this. + $tableObject = static::getCollection($tableName, [], $skipCache, $skipAcl); + $column = $tableObject->getField($columnName); + + return $column; + } + + /** + * @todo for ALTER requests, caching schemas can't be allowed + */ + + /** + * Checks whether the given table has a status column + * + * @param $tableName + * @param $skipAcl + * + * @return bool + */ + public static function hasStatusField($tableName, $skipAcl = false) + { + $schema = static::getCollection($tableName, [], false, $skipAcl); + + return $schema->hasStatusField(); + } + + /** + * Gets the status field + * + * @param $tableName + * @param $skipAcl + * + * @return null|Field + */ + public static function getStatusField($tableName, $skipAcl = false) + { + $schema = static::getCollection($tableName, [], false, $skipAcl); + + return $schema->getStatusField(); + } + + /** + * Gets the status field name + * + * @param $collectionName + * @param bool $skipAcl + * + * @return null|string + */ + public static function getStatusFieldName($collectionName, $skipAcl = false) + { + $field = static::getStatusField($collectionName, $skipAcl); + $name = null; + + if ($field) { + $name = $field->getName(); + } + + return $name; + } + + /** + * If the table has one or more relational interfaces + * + * @param $tableName + * @param array $columns + * @param bool $skipAcl + * + * @return bool + */ + public static function hasSomeRelational($tableName, array $columns, $skipAcl = false) + { + $tableSchema = static::getCollection($tableName, [], false, $skipAcl); + $relationalColumns = $tableSchema->getRelationalFieldsName(); + + $has = false; + foreach ($relationalColumns as $column) { + if (in_array($column, $columns)) { + $has = true; + break; + } + } + + return $has; + } + + /** + * Gets tehe column relationship type + * + * @param $tableName + * @param $columnName + * + * @return null|string + */ + public static function getColumnRelationshipType($tableName, $columnName) + { + $relationship = static::getColumnRelationship($tableName, $columnName); + + $relationshipType = null; + if ($relationship) { + $relationshipType = $relationship->getType(); + } + + return $relationshipType; + } + + /** + * Gets Column's relationship + * + * @param $tableName + * @param $columnName + * + * @return FieldRelationship|null + */ + public static function getColumnRelationship($tableName, $columnName) + { + $column = static::getField($tableName, $columnName); + + return $column && $column->hasRelationship() ? $column->getRelationship() : null; + } + + /** + * Check whether the given table-column has relationship + * + * @param $tableName + * @param $columnName + * + * @return bool + * + * @throws FieldNotFoundException + */ + public static function hasRelationship($tableName, $columnName) + { + $tableObject = static::getCollection($tableName); + $columnObject = $tableObject->getField($columnName); + + if (!$columnObject) { + throw new FieldNotFoundException($columnName); + } + + return $columnObject->hasRelationship(); + } + + /** + * Gets related table name + * + * @param $tableName + * @param $columnName + * + * @return string + */ + public static function getRelatedCollectionName($tableName, $columnName) + { + if (!static::hasRelationship($tableName, $columnName)) { + return null; + } + + $tableObject = static::getCollection($tableName); + $columnObject = $tableObject->getField($columnName); + + return $columnObject->getRelationship()->getCollectionB(); + } + + /** + * Checks whether the table is a system table + * + * @param $tableName + * + * @return bool + */ + public static function isSystemCollection($tableName) + { + return static::getSchemaManagerInstance()->isSystemCollection($tableName); + } + + /** + * @param $tableName + * + * @return \Directus\Database\Schema\Object\Field[] |bool + */ + public static function getAllCollectionFields($tableName) + { + $columns = static::getSchemaManagerInstance()->getFields($tableName); + + $acl = static::getAclInstance(); + $readFieldBlacklist = $acl->getReadFieldBlacklist($tableName); + + return array_filter($columns, function (Field $column) use ($readFieldBlacklist) { + return !in_array($column->getName(), $readFieldBlacklist); + }); + } + + /** + * @param $tableName + * + * @return array + */ + public static function getAllCollectionFieldsName($tableName) + { + // @TODO: make all these methods name more standard + // TableColumnsName vs TableColumnNames + $fields = static::getAllCollectionFields($tableName); + + return array_map(function(Field $field) { + return $field->getName(); + }, $fields); + } + + public static function getAllNonAliasCollectionFieldNames($table) + { + $columnNames = []; + $columns = self::getAllNonAliasCollectionFields($table); + if (false === $columns) { + return false; + } + + foreach ($columns as $column) { + $columnNames[] = $column->getName(); + } + + return $columnNames; + } + + /** + * Gets the non alias columns from the given table name + * + * @param string $tableName + * @param bool $onlyNames + * + * @return Field[]|bool + */ + public static function getAllNonAliasCollectionFields($tableName, $onlyNames = false) + { + $columns = []; + $schemaArray = static::getAllCollectionFields($tableName); + if (false === $schemaArray) { + return false; + } + + foreach ($schemaArray as $column) { + if (!$column->isAlias()) { + $columns[] = $onlyNames === true ? $column->getName() : $column; + } + } + + return $columns; + } + + /** + * Gets the alias columns from the given table name + * + * @param string $tableName + * @param bool $onlyNames + * + * @return Field[]|bool + */ + public static function getAllAliasCollectionFields($tableName, $onlyNames = false) + { + $columns = []; + $schemaArray = static::getAllCollectionFields($tableName); + if (false === $schemaArray) { + return false; + } + + foreach ($schemaArray as $column) { + if ($column->isAlias()) { + $columns[] = $onlyNames === true ? $column->getName() : $column; + } + } + + return $columns; + } + + /** + * Gets the non alias columns name from the given table name + * + * @param string $tableName + * + * @return Field[]|bool + */ + public static function getAllNonAliasCollectionFieldsName($tableName) + { + return static::getAllNonAliasCollectionFields($tableName, true); + } + + /** + * Gets the alias columns name from the given table name + * + * @param string $tableName + * + * @return Field[]|bool + */ + public static function getAllAliasCollectionFieldsName($tableName) + { + return static::getAllAliasCollectionFields($tableName, true); + } + + public static function getCollectionFields($table, $limit = null, $skipIgnore = false) + { + if (!self::canGroupReadCollection($table)) { + return []; + } + + $schemaManager = static::getSchemaManagerInstance(); + $tableObject = $schemaManager->getCollection($table); + $columns = $tableObject->getFields(); + $columnsName = []; + $count = 0; + foreach ($columns as $column) { + if ($skipIgnore === false + && ( + ($tableObject->hasStatusField() && $column->getName() === $tableObject->getStatusField()->getName()) + || ($tableObject->hasSortingField() && $column->getName() === $tableObject->getSortingField()) + || ($tableObject->hasPrimaryField() && $column->getName() === $tableObject->getPrimaryField()) + ) + ) { + continue; + } + + // at least will return one + if ($limit && $count > $limit) { + break; + } + + $columnsName[] = $column->getName(); + $count++; + } + + return $columnsName; + } + + public static function getFieldsName($table) + { + if (isset(static::$_schemas[$table])) { + $columns = array_map(function($column) { + return $column['column_name']; + }, static::$_schemas[$table]); + } else { + $columns = static::getSchemaManagerInstance()->getFieldsName($table); + } + + $names = []; + foreach ($columns as $column) { + $names[] = $column; + } + + return $names; + } + + /** + * Checks whether or not the given table has a sort column + * + * @param $table + * @param bool $includeAlias + * + * @return bool + */ + public static function hasCollectionSortField($table, $includeAlias = false) + { + $column = static::getCollectionSortField($table); + + return static::hasCollectionField($table, $column, $includeAlias); + } + + public static function hasCollectionField($table, $column, $includeAlias = false, $skipAcl = false) + { + $tableObject = static::getCollection($table, [], false, $skipAcl); + + $columns = $tableObject->getNonAliasFieldsName(); + if ($includeAlias) { + $columns = array_merge($columns, $tableObject->getAliasFieldsName()); + } + + if (in_array($column, $columns)) { + return true; + } + + return false; + } + + /** + * Gets the table sort column name + * + * @param $table + * + * @return string + */ + public static function getCollectionSortField($table) + { + $tableObject = static::getCollection($table); + + $sortColumnName = $tableObject->getSortingField(); + if (!$sortColumnName) { + $sortColumnName = $tableObject->getPrimaryKeyName() ?: 'id'; + } + + return $sortColumnName; + } + + /** + * Has the authenticated user permission to view the given table + * + * @param $tableName + * + * @return bool + */ + public static function canGroupReadCollection($tableName) + { + $acl = static::getAclInstance(); + + if (! $acl) { + return true; + } + + return $acl->canRead($tableName); + } + + /** + * Has the authenticated user permissions to read the given column + * + * @param $tableName + * @param $columnName + * + * @return bool + */ + public static function canReadField($tableName, $columnName) + { + $acl = static::getAclInstance(); + + if (! $acl) { + return true; + } + + return $acl->canReadField($tableName, $columnName); + } + + /** + * Get table primary key + * @param $tableName + * @return String|boolean - column name or false + */ + public static function getCollectionPrimaryKey($tableName) + { + if (isset(self::$_primaryKeys[$tableName])) { + return self::$_primaryKeys[$tableName]; + } + + $schemaManager = static::getSchemaManagerInstance(); + + $columnName = $schemaManager->getPrimaryKey($tableName); + + return self::$_primaryKeys[$tableName] = $columnName; + } + + protected static function createParamArray($values, $prefix) + { + $result = []; + + foreach ($values as $i => $field) { + $result[$prefix . $i] = $field; + } + + return $result; + } +} diff --git a/src/core/Directus/Database/TableGateway/BaseTableGateway.php b/src/core/Directus/Database/TableGateway/BaseTableGateway.php new file mode 100644 index 0000000000..e3a4902fcd --- /dev/null +++ b/src/core/Directus/Database/TableGateway/BaseTableGateway.php @@ -0,0 +1,1628 @@ +table = $table; + $this->acl = $acl; + + // @NOTE: temporary, do we need it here? + if ($this->primaryKeyFieldName === null) { + if ($primaryKeyName !== null) { + $this->primaryKeyFieldName = $primaryKeyName; + } else { + $tableObject = $this->getTableSchema(); + if ($tableObject->getPrimaryField()) { + $this->primaryKeyFieldName = $tableObject->getPrimaryField()->getName(); + } + } + } + + // @NOTE: This will be substituted by a new Cache wrapper class + // $this->memcache = new MemcacheProvider(); + if ($features === null) { + $features = new Feature\FeatureSet(); + } else if ($features instanceof Feature\AbstractFeature) { + $features = [$features]; + } else if (is_array($features)) { + $features = new Feature\FeatureSet($features); + } + + $rowGatewayPrototype = new BaseRowGateway($this->primaryKeyFieldName, $table, $adapter, $this->acl); + $rowGatewayFeature = new RowGatewayFeature($rowGatewayPrototype); + $features->addFeature($rowGatewayFeature); + + parent::__construct($table, $adapter, $features, $resultSetPrototype, $sql); + + if (static::$container) { + $this->schemaManager = static::$container->get('schema_manager'); + } + } + + /** + * Static Factory Methods + */ + + /** + * Creates a table gateway based on a table's name + * + * Underscore to camelcase table name to namespaced table gateway classname, + * e.g. directus_users => \Directus\Database\TableGateway\DirectusUsersTableGateway + * + * @param string $table + * @param AdapterInterface $adapter + * @param null $acl + * + * @return RelationalTableGateway + */ + public static function makeTableGatewayFromTableName($table, $adapter, $acl = null) + { + return TableGatewayFactory::create($table, [ + 'adapter' => $adapter, + 'acl' => $acl + ]); + } + + /** + * Make a new table gateway + * + * @param string $tableName + * @param AdapterInterface $adapter + * @param Acl $acl + * + * @return BaseTableGateway + */ + public function makeTable($tableName, $adapter = null, $acl = null) + { + $adapter = is_null($adapter) ? $this->adapter : $adapter; + $acl = is_null($acl) ? $this->acl : $acl; + + return static::makeTableGatewayFromTableName($tableName, $adapter, $acl); + } + + public function getTableSchema($tableName = null) + { + if ($this->tableSchema !== null && ($tableName === null || $tableName === $this->getTable())) { + return $this->tableSchema; + } + + if ($tableName === null) { + $tableName = $this->getTable(); + } + + $skipAcl = $this->acl === null; + $tableSchema = SchemaService::getCollection($tableName, [], false, $skipAcl); + + if ($tableName === $this->getTable()) { + $this->tableSchema = $tableSchema; + } + + return $tableSchema; + } + + /** + * Gets the column schema (object) + * + * @param $columnName + * @param null $tableName + * + * @return Field + */ + public function getField($columnName, $tableName = null) + { + if ($tableName === null) { + $tableName = $this->getTable(); + } + + $skipAcl = $this->acl === null; + + return SchemaService::getField($tableName, $columnName, false, $skipAcl); + } + + /** + * Gets the status column name + * + * @return string + */ + public function getStatusFieldName() + { + return $this->getTableSchema()->getStatusField(); + } + + public function withKey($key, $resultSet) + { + $withKey = []; + foreach ($resultSet as $row) { + $withKey[$row[$key]] = $row; + } + return $withKey; + } + + /** + * Create a new row + * + * @param null $table + * @param null $primaryKeyColumn + * + * @return BaseRowGateway + */ + public function newRow($table = null, $primaryKeyColumn = null) + { + $table = is_null($table) ? $this->table : $table; + $primaryKeyColumn = is_null($primaryKeyColumn) ? $this->primaryKeyFieldName : $primaryKeyColumn; + $row = new BaseRowGateway($primaryKeyColumn, $table, $this->adapter, $this->acl); + + return $row; + } + + public function find($id, $pk_field_name = null) + { + if ($pk_field_name == null) { + $pk_field_name = $this->primaryKeyFieldName; + } + + $record = $this->findOneBy($pk_field_name, $id); + + return $record ? $this->parseRecordValuesByType($record) : null; + } + + public function fetchAll($selectModifier = null) + { + return $this->select(function (Select $select) use ($selectModifier) { + if (is_callable($selectModifier)) { + $selectModifier($select); + } + }); + } + + /** + * @return array All rows in array form with record IDs for the array's keys. + */ + public function fetchAllWithIdKeys($selectModifier = null) + { + $allWithIdKeys = []; + $all = $this->fetchAll($selectModifier)->toArray(); + return $this->withKey('id', $all); + } + + public function findOneBy($field, $value) + { + $rowset = $this->ignoreFilters()->select(function (Select $select) use ($field, $value) { + $select->limit(1); + $select->where->equalTo($field, $value); + }); + + $row = $rowset->current(); + // Supposing this "one" doesn't exist in the DB + if (!$row) { + return false; + } + + return $row->toArray(); + } + + public function findOneByArray(array $data) + { + $rowset = $this->select($data); + + $row = $rowset->current(); + // Supposing this "one" doesn't exist in the DB + if (!$row) { + return false; + } + + return $row->toArray(); + } + + public function addOrUpdateRecordByArray(array $recordData, $collectionName = null) + { + $collectionName = is_null($collectionName) ? $this->table : $collectionName; + $collectionObject = $this->getTableSchema($collectionName); + foreach ($recordData as $columnName => $columnValue) { + $fieldObject = $collectionObject->getField($columnName); + // TODO: Should this be validate in here? should we let the database fails? + if (($fieldObject && is_array($columnValue) && (!$fieldObject->isJson() && !$fieldObject->isArray()))) { + // $table = is_null($tableName) ? $this->table : $tableName; + throw new SuppliedArrayAsColumnValue('Attempting to write an array as the value for column `' . $collectionName . '`.`' . $columnName . '.'); + } + } + + // @TODO: Dow we need to parse before insert? + // Commented out because date are not saved correctly in GMT + // $recordData = $this->parseRecord($recordData); + + $TableGateway = $this->makeTable($collectionName); + $primaryKey = $TableGateway->primaryKeyFieldName; + $hasPrimaryKeyData = isset($recordData[$primaryKey]); + $rowExists = false; + $currentItem = null; + $originalFilename = null; + + if ($hasPrimaryKeyData) { + $select = new Select($collectionName); + $select->columns(['*']); + $select->where([ + $primaryKey => $recordData[$primaryKey] + ]); + $select->limit(1); + $result = $TableGateway->ignoreFilters()->selectWith($select); + $rowExists = $result->count() > 0; + if ($rowExists) { + $currentItem = $result->current()->toArray(); + } + + if ($collectionName === SchemaManager::COLLECTION_FILES) { + $originalFilename = ArrayUtils::get($currentItem, 'filename'); + $recordData = array_merge([ + 'filename' => $originalFilename + ], $recordData); + } + } + + $afterAction = function ($collectionName, $recordData, $replace = false) use ($TableGateway) { + if ($collectionName == SchemaManager::COLLECTION_FILES && static::$container) { + $Files = static::$container->get('files'); + + $updateArray = []; + if ($Files->getSettings('file_naming') == 'file_id') { + $ext = $thumbnailExt = pathinfo($recordData['filename'], PATHINFO_EXTENSION); + $Files->rename($recordData['filename'], str_pad($recordData[$this->primaryKeyFieldName], 11, '0', STR_PAD_LEFT) . '.' . $ext, $replace); + $updateArray['filename'] = str_pad($recordData[$this->primaryKeyFieldName], 11, '0', STR_PAD_LEFT) . '.' . $ext; + $recordData['filename'] = $updateArray['filename']; + } + + if (!empty($updateArray)) { + $Update = new Update($collectionName); + $Update->set($updateArray); + $Update->where([$TableGateway->primaryKeyFieldName => $recordData[$TableGateway->primaryKeyFieldName]]); + $TableGateway->updateWith($Update); + } + } + }; + + if ($rowExists) { + $Update = new Update($collectionName); + $Update->set($recordData); + $Update->where([ + $primaryKey => $recordData[$primaryKey] + ]); + $TableGateway->updateWith($Update); + + if ($collectionName == 'directus_files' && static::$container) { + if ($originalFilename && $recordData['filename'] !== $originalFilename) { + /** @var Files $Files */ + $Files = static::$container->get('files'); + $Files->delete(['filename' => $originalFilename]); + } + } + + $afterAction($collectionName, $recordData, true); + + $this->runHook('postUpdate', [$TableGateway, $recordData, $this->adapter, null]); + } else { + $recordData = $this->applyHook('collection.insert:before', $recordData, [ + 'collection_name' => $collectionName + ]); + $recordData = $this->applyHook('collection.insert.' . $collectionName . ':before', $recordData); + $TableGateway->insert($recordData); + + // Only get the last inserted id, if the column has auto increment value + $columnObject = $this->getTableSchema()->getField($primaryKey); + if ($columnObject->hasAutoIncrement()) { + $recordData[$primaryKey] = $TableGateway->getLastInsertValue(); + } + + $afterAction($collectionName, $recordData); + + $this->runHook('postInsert', [$TableGateway, $recordData, $this->adapter, null]); + } + + $columns = SchemaService::getAllNonAliasCollectionFieldNames($collectionName); + $recordData = $TableGateway->fetchAll(function ($select) use ($recordData, $columns, $primaryKey) { + $select + ->columns($columns) + ->limit(1); + $select->where->equalTo($primaryKey, $recordData[$primaryKey]); + })->current(); + + return $recordData; + } + + public function drop($tableName = null) + { + if ($tableName == null) { + $tableName = $this->table; + } + + if ($this->acl) { + $this->acl->enforceAlter($tableName); + } + + $dropped = false; + if ($this->schemaManager->tableExists($tableName)) { + // get drop table query + $sql = new Sql($this->adapter); + $drop = new Ddl\DropTable($tableName); + $query = $sql->buildSqlString($drop); + + $this->runHook('collection.drop:before', [$tableName]); + + $dropped = $this->getAdapter()->query( + $query + )->execute(); + + $this->runHook('collection.drop', [$tableName]); + $this->runHook('collection.drop:after', [$tableName]); + } + + $this->stopManaging(); + + return $dropped; + } + + /** + * Stop managing a table by removing privileges, preferences columns and table information + * + * @param null $tableName + * + * @return bool + */ + public function stopManaging($tableName = null) + { + if ($tableName == null) { + $tableName = $this->table; + } + + // Remove table privileges + if ($tableName != SchemaManager::COLLECTION_PERMISSIONS) { + $privilegesTableGateway = new TableGateway(SchemaManager::COLLECTION_PERMISSIONS, $this->adapter); + $privilegesTableGateway->delete(['collection' => $tableName]); + } + + // Remove columns from directus_columns + $columnsTableGateway = new TableGateway(SchemaManager::COLLECTION_FIELDS, $this->adapter); + $columnsTableGateway->delete([ + 'collection' => $tableName + ]); + + // Remove table from directus_tables + $tablesTableGateway = new TableGateway(SchemaManager::COLLECTION_COLLECTIONS, $this->adapter); + $tablesTableGateway->delete([ + 'collection' => $tableName + ]); + + // Remove table from directus_collection_presets + $preferencesTableGateway = new TableGateway(SchemaManager::COLLECTION_COLLECTION_PRESETS, $this->adapter); + $preferencesTableGateway->delete([ + 'collection' => $tableName + ]); + + return true; + } + + public function dropField($columnName, $tableName = null) + { + if ($tableName == null) { + $tableName = $this->table; + } + + if ($this->acl) { + $this->acl->enforceAlter($tableName); + } + + if (!SchemaService::hasCollectionField($tableName, $columnName, true)) { + return false; + } + + // Drop table column if is a non-alias column + if (!array_key_exists($columnName, array_flip(SchemaService::getAllAliasCollectionFields($tableName, true)))) { + $sql = new Sql($this->adapter); + $alterTable = new Ddl\AlterTable($tableName); + $dropColumn = $alterTable->dropColumn($columnName); + $query = $sql->getSqlStringForSqlObject($dropColumn); + + $this->adapter->query( + $query + )->execute(); + } + + // Remove column from directus_columns + $columnsTableGateway = new TableGateway(SchemaManager::COLLECTION_FIELDS, $this->adapter); + $columnsTableGateway->delete([ + 'table_name' => $tableName, + 'column_name' => $columnName + ]); + + // Remove column from sorting column in directus_preferences + $preferencesTableGateway = new TableGateway(SchemaManager::COLLECTION_COLLECTION_PRESETS, $this->adapter); + $preferencesTableGateway->update([ + 'sort' => $this->primaryKeyFieldName, + 'sort_order' => 'ASC' + ], [ + 'table_name' => $tableName, + 'sort' => $columnName + ]); + + return true; + } + + /* + Temporary solutions to fix add column error + This add column is the same old-db add_column method + */ + public function addColumn($tableName, $tableData) + { + // @TODO: enforce permission + $directus_types = ['MANYTOMANY', 'ONETOMANY', 'ALIAS']; + $relationshipType = ArrayUtils::get($tableData, 'relationship_type', null); + // TODO: list all types which need manytoone ui + // Hard-coded + $manytoones = ['single_file', 'many_to_one', 'many_to_one_typeahead', 'MANYTOONE']; + + if (!in_array($relationshipType, $directus_types)) { + $this->addTableColumn($tableName, $tableData); + // Temporary solutions to #481, #645 + if (array_key_exists('ui', $tableData) && in_array($tableData['ui'], $manytoones)) { + $tableData['relationship_type'] = 'MANYTOONE'; + $tableData['junction_key_right'] = $tableData['column_name']; + } + } + + //This is a 'virtual column'. Write to directus schema instead of MYSQL + $this->addVirtualColumn($tableName, $tableData); + + return $tableData['column_name']; + } + + // @TODO: TableGateway should not be handling table creation + protected function addTableColumn($tableName, $columnData) + { + $column_name = $columnData['column_name']; + $dataType = $columnData['data_type']; + $comment = $this->getAdapter()->getPlatform()->quoteValue(ArrayUtils::get($columnData, 'comment', '')); + + if (array_key_exists('length', $columnData)) { + $charLength = $columnData['length']; + // SET and ENUM data type has its values in the char_length attribute + // each value are separated by commas + // it must be wrap into quotes + if (!$this->schemaManager->isFloatingPointType($dataType) && strpos($charLength, ',') !== false) { + $charLength = implode(',', array_map(function ($value) { + return '"' . trim($value) . '"'; + }, explode(',', $charLength))); + } + + $dataType = $dataType . '(' . $charLength . ')'; + } + + $default = ''; + if (ArrayUtils::get($columnData, 'default_value')) { + $value = ArrayUtils::get($columnData, 'default_value'); + $length = ArrayUtils::get($columnData, 'length'); + $defaultValue = $this->schemaManager->castDefaultValue($value, $dataType, $length); + + $default = ' DEFAULT ' . (is_string($defaultValue) ? sprintf('"%s"', $defaultValue) : $defaultValue); + } + + // TODO: wrap this into an abstract DDL class + $sql = 'ALTER TABLE `' . $tableName . '` ADD COLUMN `' . $column_name . '` ' . $dataType . $default . ' COMMENT "' . $comment . '"'; + + $this->adapter->query($sql)->execute(); + } + + protected function addVirtualColumn($tableName, $columnData) + { + $alias_columns = ['table_name', 'column_name', 'data_type', 'related_table', 'junction_table', 'junction_key_left', 'junction_key_right', 'sort', 'ui', 'comment', 'relationship_type']; + + $columnData['table_name'] = $tableName; + // NOTE: setting 9999 as default just because + $columnData['sort'] = ArrayUtils::get($columnData, 'sort', 9999); + + $data = array_intersect_key($columnData, array_flip($alias_columns)); + return $this->addOrUpdateRecordByArray($data, 'directus_columns'); + } + + public function castFloatIfNumeric(&$value, $key) + { + if ($key != 'table_name') { + $value = is_numeric($value) ? (float)$value : $value; + } + } + + /** + * Convenience method for dumping a ZendDb Sql query object as debug output. + * + * @param SqlInterface $query + * + * @return null + */ + public function dumpSql(SqlInterface $query) + { + $sql = new Sql($this->adapter); + $query = $sql->getSqlStringForSqlObject($query, $this->adapter->getPlatform()); + return $query; + } + + public function ignoreFilters() + { + $this->options['filter'] = false; + + return $this; + } + + /** + * @param Select $select + * + * @return ResultSet + * + * @throws \Directus\Permissions\Exception\ForbiddenFieldReadException + * @throws \Directus\Permissions\Exception\ForbiddenFieldWriteException + * @throws \Exception + */ + protected function executeSelect(Select $select) + { + $useFilter = ArrayUtils::get($this->options, 'filter', true) !== false; + unset($this->options['filter']); + + if ($this->acl) { + $this->enforceSelectPermission($select); + } + + $selectState = $select->getRawState(); + $selectCollectionName = $selectState['table']; + + if ($useFilter) { + $selectState = $this->applyHooks([ + 'collection.select:before', + 'collection.select.' . $selectCollectionName . ':before', + ], $selectState, [ + 'collection_name' => $selectCollectionName + ]); + + // NOTE: This can be a "dangerous" hook, so for now we only support columns + $select->columns(ArrayUtils::get($selectState, 'columns', ['*'])); + } + + try { + $result = parent::executeSelect($select); + } catch (UnexpectedValueException $e) { + throw new InvalidQueryException( + $this->dumpSql($select), + $e + ); + } + + if ($useFilter) { + $result = $this->applyHooks([ + 'collection.select', + 'collection.select.' . $selectCollectionName + ], $result, [ + 'selectState' => $selectState, + 'collection_name' => $selectCollectionName + ]); + } + + return $result; + } + + /** + * @param Insert $insert + * + * @return mixed + * + * @throws \Directus\Database\Exception\InvalidQueryException + */ + protected function executeInsert(Insert $insert) + { + if ($this->acl) { + $this->enforceInsertPermission($insert); + } + + $insertState = $insert->getRawState(); + $insertTable = $this->getRawTableNameFromQueryStateTable($insertState['table']); + $insertData = $insertState['values']; + // Data to be inserted with the column name as assoc key. + $insertDataAssoc = array_combine($insertState['columns'], $insertData); + + $this->runHook('collection.insert:before', [$insertTable, $insertDataAssoc]); + $this->runHook('collection.insert.' . $insertTable . ':before', [$insertDataAssoc]); + + try { + $result = parent::executeInsert($insert); + } catch (UnexpectedValueException $e) { + if ( + strtolower($this->adapter->platform->getName()) === 'mysql' + && strpos(strtolower($e->getMessage()), 'duplicate entry') !== false + ) { + preg_match("/Duplicate entry '([^']+)' for key '([^']+)'/i", $e->getMessage(), $output); + + if ($output) { + throw new DuplicateItemException($this->table, $output[1]); + } + } + + throw new InvalidQueryException( + $this->dumpSql($insert), + $e + ); + } + + $insertTableGateway = $this->makeTable($insertTable); + + // hotfix: directus_tables does not have auto generated value primary key + if ($this->getTable() === SchemaManager::COLLECTION_COLLECTIONS) { + $generatedValue = ArrayUtils::get($insertDataAssoc, $this->primaryKeyFieldName, 'table_name'); + } else { + $generatedValue = $this->getLastInsertValue(); + } + + $resultData = $insertTableGateway->find($generatedValue); + + $this->runHook('collection.insert', [$insertTable, $resultData]); + $this->runHook('collection.insert.' . $insertTable, [$resultData]); + $this->runHook('collection.insert:after', [$insertTable, $resultData]); + $this->runHook('collection.insert.' . $insertTable . ':after', [$resultData]); + + return $result; + } + + /** + * @param Update $update + * + * @return mixed + * + * @throws \Directus\Database\Exception\InvalidQueryException + */ + protected function executeUpdate(Update $update) + { + $useFilter = ArrayUtils::get($this->options, 'filter', true) !== false; + unset($this->options['filter']); + + if ($this->acl) { + $this->enforceUpdatePermission($update); + } + + $updateState = $update->getRawState(); + $updateTable = $this->getRawTableNameFromQueryStateTable($updateState['table']); + $updateData = $updateState['set']; + + if ($useFilter) { + $updateData = $this->runBeforeUpdateHooks($updateTable, $updateData); + } + + $update->set($updateData); + + try { + $result = parent::executeUpdate($update); + } catch (UnexpectedValueException $e) { + throw new InvalidQueryException( + $this->dumpSql($update), + $e + ); + } + + if ($useFilter) { + $this->runAfterUpdateHooks($updateTable, $updateData); + } + + return $result; + } + + /** + * @param Delete $delete + * + * @return mixed + * + * @throws \Directus\Database\Exception\InvalidQueryException + */ + protected function executeDelete(Delete $delete) + { + $ids = []; + + if ($this->acl) { + $this->enforceDeletePermission($delete); + } + + $deleteState = $delete->getRawState(); + $deleteTable = $this->getRawTableNameFromQueryStateTable($deleteState['table']); + + // Runs select PK with passed delete's $where before deleting, to use those for the even hook + if ($pk = $this->primaryKeyFieldName) { + $select = $this->sql->select(); + $select->where($deleteState['where']); + $select->columns([$pk]); + $results = parent::executeSelect($select); + + foreach($results as $result) { + $ids[] = $result['id']; + } + } + + // skipping everything, if there is nothing to delete + if ($ids) { + $delete = $this->sql->delete(); + $expression = new In($pk, $ids); + $delete->where($expression); + + foreach ($ids as $id) { + $deleteData = ['id' => $id]; + $this->runHook('collection.delete:before', [$deleteTable, $deleteData]); + $this->runHook('collection.delete.' . $deleteTable . ':before', [$deleteData]); + } + + try { + $result = parent::executeDelete($delete); + } catch (UnexpectedValueException $e) { + throw new InvalidQueryException( + $this->dumpSql($delete), + $e + ); + } + + foreach ($ids as $id) { + $deleteData = ['id' => $id]; + $this->runHook('collection.delete', [$deleteTable, $deleteData]); + $this->runHook('collection.delete:after', [$deleteTable, $deleteData]); + $this->runHook('collection.delete.' . $deleteTable, [$deleteData]); + $this->runHook('collection.delete.' . $deleteTable . ':after', [$deleteData]); + } + + return $result; + } + } + + protected function getRawTableNameFromQueryStateTable($table) + { + if (is_string($table)) { + return $table; + } + + if (is_array($table)) { + // The only value is the real table name (key is alias). + return array_pop($table); + } + + throw new \InvalidArgumentException('Unexpected parameter of type ' . get_class($table)); + } + + /** + * Convert dates to ISO 8601 format + * + * @param array $records + * @param Collection $tableSchema + * @param null $tableName + * + * @return array|mixed + */ + public function convertDates(array $records, Collection $tableSchema, $tableName = null) + { + $tableName = $tableName === null ? $this->table : $tableName; + $isCustomTable = !$this->schemaManager->isDirectusCollection($tableName); + $hasSystemDateColumn = $this->schemaManager->hasSystemDateField($tableName); + + if (!$hasSystemDateColumn && $isCustomTable) { + return $records; + } + + // ========================================================================== + // hotfix: records sometimes are no set as an array of rows. + // NOTE: this code is duplicate @see: AbstractSchema::parseRecordValuesByType + // ========================================================================== + $singleRecord = false; + if (!ArrayUtils::isNumericKeys($records)) { + $records = [$records]; + $singleRecord = true; + } + + foreach ($records as $index => $row) { + foreach ($tableSchema->getFields() as $column) { + $canConvert = in_array(strtolower($column->getType()), ['timestamp', 'datetime']); + // Directus convert all dates to ISO to all datetime columns in the core tables + // and any columns using system date interfaces (datetime_created or datetime_modified) + if ($isCustomTable && !$column->isSystemDateType()) { + $canConvert = false; + } + + if ($canConvert) { + $columnName = $column->getName(); + + if (isset($row[$columnName])) { + $datetime = DateTimeUtils::createFromDefaultFormat($row[$columnName], 'UTC'); + $records[$index][$columnName] = $datetime->toISO8601Format(); + } + } + } + } + + return $singleRecord ? reset($records) : $records; + } + + /** + * Parse records value by its column type + * + * @param array $records + * @param null $tableName + * + * @return array + */ + protected function parseRecordValuesByType(array $records, $tableName = null) + { + // NOTE: Performance spot + $tableName = $tableName === null ? $this->table : $tableName; + // Get the columns directly from the source + // otherwise will keep in a circle loop loading Acl Instances + $columns = SchemaService::getSchemaManagerInstance()->getFields($tableName); + + return $this->schemaManager->castRecordValues($records, $columns); + } + + /** + * Parse Records values (including format date by ISO 8601) by its column type + * + * @param $records + * @param null $tableName + * + * @return array|mixed + */ + public function parseRecord($records, $tableName = null) + { + // NOTE: Performance spot + if (is_array($records)) { + $tableName = $tableName === null ? $this->table : $tableName; + $records = $this->parseRecordValuesByType($records, $tableName); + $tableSchema = $this->getTableSchema($tableName); + $records = $this->convertDates($records, $tableSchema, $tableName); + } + + return $records; + } + + /** + * Enforce permission on Select + * + * @param Select $select + * + * @throws \Exception + */ + protected function enforceSelectPermission(Select $select) + { + $selectState = $select->getRawState(); + $table = $this->getRawTableNameFromQueryStateTable($selectState['table']); + + // @TODO: enforce view permission + + // Enforce field read blacklist on Select's main table + try { + // @TODO: Enforce must return a list of columns without the blacklist + // when asterisk (*) is used + // and only throw and error when all the selected columns are blacklisted + $this->acl->enforceReadField($table, $selectState['columns']); + } catch (\Exception $e) { + if ($selectState['columns'][0] != '*') { + throw $e; + } + + $selectState['columns'] = SchemaService::getAllNonAliasCollectionFieldsName($table); + $this->acl->enforceReadField($table, $selectState['columns']); + } + + // Enforce field read blacklist on Select's join tables + foreach ($selectState['joins'] as $join) { + $joinTable = $this->getRawTableNameFromQueryStateTable($join['name']); + $this->acl->enforceReadField($joinTable, $join['columns']); + } + } + + /** + * Enforce permission on Insert + * + * @param Insert $insert + * + * @throws \Exception + */ + public function enforceInsertPermission(Insert $insert) + { + $insertState = $insert->getRawState(); + $insertTable = $this->getRawTableNameFromQueryStateTable($insertState['table']); + + $statusValue = null; + $statusField = $this->getTableSchema()->getStatusField(); + if ($statusField) { + $valueKey = array_search($statusField->getName(), $insertState['columns']); + if ($valueKey !== false) {; + $statusValue = ArrayUtils::get($insertState['values'], $valueKey); + } else { + $statusValue = $statusField->getDefaultValue(); + } + } + + $this->acl->enforceCreate($insertTable, $statusValue); + } + + /** + * @param Builder $builder + */ + protected function enforceReadPermission(Builder $builder) + { + // ---------------------------------------------------------------------------- + // Make sure the user has permission to at least their items + // ---------------------------------------------------------------------------- + $this->acl->enforceReadOnce($this->table); + $collectionObject = $this->getTableSchema(); + $userCreatedField = $collectionObject->getUserCreatedField(); + $statusField = $collectionObject->getStatusField(); + + // If there's not user created interface, user must have full read permission + if (!$userCreatedField && !$statusField) { + $this->acl->enforceReadAll($this->table); + return; + } + + // User can read all items, nothing else to check + if ($this->acl->canReadAll($this->table)) { + return; + } + + $groupUsersId = get_user_ids_in_group($this->acl->getRolesId()); + $authenticatedUserId = $this->acl->getUserId(); + $statuses = $this->acl->getCollectionStatuses($this->table); + + if (empty($statuses)) { + $ownerIds = [$authenticatedUserId]; + if ($this->acl->canReadFromGroup($this->table)) { + $ownerIds = array_merge( + $ownerIds, + $groupUsersId + ); + } + + $builder->whereIn($userCreatedField->getName(), $ownerIds); + } else { + $collection = $this->table; + $builder->nestWhere(function (Builder $builder) use ($collection, $statuses, $statusField, $userCreatedField, $groupUsersId, $authenticatedUserId) { + foreach ($statuses as $status) { + $canReadAll = $this->acl->canReadAll($collection, $status); + $canReadMine = $this->acl->canReadMine($collection, $status); + + if ((!$canReadAll && !$userCreatedField) || !$canReadMine) { + continue; + } + + $ownerIds = $canReadAll ? null : [$authenticatedUserId]; + $canReadFromGroup = $this->acl->canReadFromGroup($collection, $status); + if (!$canReadAll && $canReadFromGroup) { + $ownerIds = array_merge( + $ownerIds, + $groupUsersId + ); + } + + $builder->nestOrWhere(function (Builder $builder) use ($statuses, $ownerIds, $statusField, $userCreatedField, $status) { + if ($ownerIds) { + $builder->whereIn($userCreatedField->getName(), $ownerIds); + } + + $builder->whereEqualTo($statusField->getName(), $status); + }); + } + + + }); + } + } + + /** + * Enforce permission on Update + * + * @param Update $update + * + * @throws \Exception + */ + public function enforceUpdatePermission(Update $update) + { + if ($this->acl->canUpdateAll($this->table) && $this->acl->isAdmin()) { + return; + } + + $collectionObject = $this->getTableSchema(); + $currentUserId = $this->acl->getUserId(); + $updateState = $update->getRawState(); + $updateTable = $this->getRawTableNameFromQueryStateTable($updateState['table']); + $select = $this->sql->select(); + $select->where($updateState['where']); + $select->limit(1); + $item = $this->ignoreFilters()->selectWith($select)->toArray(); + $item = reset($item); + $statusId = null; + + // Item not found, item cannot be updated + if (!$item) { + throw new ForbiddenCollectionUpdateException($updateTable); + } + + // Enforce write field blacklist + $this->acl->enforceWriteField($updateTable, array_keys($updateState['set'])); + + if ($collectionObject->hasStatusField()) { + $statusField = $this->getTableSchema()->getStatusField(); + $statusId = $item[$statusField->getName()]; + + // non-admins cannot update soft-deleted items + $status = $this->getStatusMapping()->getByValue($statusId); + if ($status && $status->isSoftDelete()) { + throw new ForbiddenCollectionUpdateException($updateTable); + } + } + + // User Created Interface not found, item cannot be updated + $itemOwnerField = $this->getTableSchema()->getUserCreatedField(); + if (!$itemOwnerField) { + $this->acl->enforceUpdateAll($updateTable, $statusId); + return; + } + + // Owner not found, item cannot be updated + $owner = get_item_owner($updateTable, $item[$collectionObject->getPrimaryKeyName()]); + if (!is_array($owner)) { + throw new ForbiddenCollectionUpdateException($updateTable); + } + + $userItem = $currentUserId === $owner['id']; + $hasRole = $this->acl->hasRole($owner['role']); + if (!$userItem && !$hasRole && !$this->acl->canUpdateAll($updateTable, $statusId)) { + throw new ForbiddenCollectionUpdateException($updateTable); + } + + if (!$userItem && $hasRole) { + $this->acl->enforceUpdateFromGroup($updateTable, $statusId); + } else if ($userItem) { + $this->acl->enforceUpdate($updateTable, $statusId); + } + } + + /** + * Enforce permission on Delete + * + * @param Delete $delete + * + * @throws ForbiddenCollectionDeleteException + */ + public function enforceDeletePermission(Delete $delete) + { + $collectionObject = $this->getTableSchema(); + $currentUserId = $this->acl->getUserId(); + $deleteState = $delete->getRawState(); + $deleteTable = $this->getRawTableNameFromQueryStateTable($deleteState['table']); + // $cmsOwnerColumn = $this->acl->getCmsOwnerColumnByTable($deleteTable); + // $canBigDelete = $this->acl->hasTablePrivilege($deleteTable, 'bigdelete'); + // $canDelete = $this->acl->hasTablePrivilege($deleteTable, 'delete'); + // $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); + + $select = $this->sql->select(); + $select->where($deleteState['where']); + $select->limit(1); + $item = $this->ignoreFilters()->selectWith($select)->toArray(); + $item = reset($item); + $statusId = null; + + // Item not found, item cannot be updated + if (!$item) { + throw new ItemNotFoundException(); + } + + if ($collectionObject->hasStatusField()) { + $statusField = $this->getTableSchema()->getStatusField(); + $statusId = $item[$statusField->getName()]; + } + + // User Created Interface not found, item cannot be updated + $itemOwnerField = $this->getTableSchema()->getUserCreatedField(); + if (!$itemOwnerField) { + $this->acl->enforceDeleteAll($deleteTable, $statusId); + return; + } + + // Owner not found, item cannot be updated + $owner = get_item_owner($deleteTable, $item[$collectionObject->getPrimaryKeyName()]); + if (!is_array($owner)) { + throw new ForbiddenCollectionDeleteException($deleteTable); + } + + $userItem = $currentUserId === $owner['id']; + $hasRole = $this->acl->hasRole($owner['role']); + if (!$userItem && !$hasRole && !$this->acl->canDeleteAll($deleteTable, $statusId)) { + throw new ForbiddenCollectionDeleteException($deleteTable); + } + + if (!$userItem && $hasRole) { + $this->acl->enforceDeleteFromGroup($deleteTable, $statusId); + } else if ($userItem) { + $this->acl->enforceDelete($deleteTable, $statusId); + } + + // @todo: clean way + // @TODO: this doesn't need to be bigdelete + // the user can only delete their own entry + // if ($deleteTable === 'directus_bookmarks') { + // $canBigDelete = true; + // } + + // @TODO: Update conditions + // ============================================================================= + // Cannot delete if there's no magic owner column and can't big delete + // All deletes are "big" deletes if there is no magic owner column. + // ============================================================================= + // if (false === $cmsOwnerColumn && !$canBigDelete) { + // throw new ForbiddenCollectionDeleteException($aclErrorPrefix . 'The table `' . $deleteTable . '` is missing the `user_create_column` within `directus_collections` (BigHardDelete Permission Forbidden)'); + // } else if (!$canBigDelete) { + // // Who are the owners of these rows? + // list($predicateResultQty, $predicateOwnerIds) = $this->acl->getCmsOwnerIdsByTableGatewayAndPredicate($this, $deleteState['where']); + // if (!in_array($currentUserId, $predicateOwnerIds)) { + // // $exceptionMessage = "Table harddelete access forbidden on $predicateResultQty `$deleteTable` table records owned by the authenticated CMS user (#$currentUserId)."; + // $groupsTableGateway = $this->makeTable('directus_roles'); + // $group = $groupsTableGateway->find($this->acl->getGroupId()); + // $exceptionMessage = '[' . $group['name'] . '] permissions only allow you to [delete] your own items.'; + // // $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); + // throw new ForbiddenCollectionDeleteException($exceptionMessage); + // } + // } + } + + /** + * Get the column identifier with the specific quote and table prefixed + * + * @param string $column + * @param string|null $table + * + * @return string + */ + public function getColumnIdentifier($column, $table = null) + { + $platform = $this->getAdapter()->getPlatform(); + + // TODO: find a common place to share this code + // It is a duplicated code from Builder.php + if (strpos($column, $platform->getIdentifierSeparator()) === false) { + $column = implode($platform->getIdentifierSeparator(), [$table, $column]); + } + + return $column; + } + + /** + * Get the column name from the identifier + * + * @param string $column + * + * @return string + */ + public function getColumnFromIdentifier($column) + { + $platform = $this->getAdapter()->getPlatform(); + + // TODO: find a common place to share this code + // It is duplicated code in Builder.php + if (strpos($column, $platform->getIdentifierSeparator()) !== false) { + $identifierParts = explode($platform->getIdentifierSeparator(), $column); + $column = array_pop($identifierParts); + } + + return $column; + } + + /** + * Get the table name from the identifier + * + * @param string $column + * @param string|null $table + * + * @return string + */ + public function getTableFromIdentifier($column, $table = null) + { + $platform = $this->getAdapter()->getPlatform(); + + if ($table === null) { + $table = $this->getTable(); + } + + // TODO: find a common place to share this code + // It is duplicated code in Builder.php + if (strpos($column, $platform->getIdentifierSeparator()) !== false) { + $identifierParts = explode($platform->getIdentifierSeparator(), $column); + $table = array_shift($identifierParts); + } + + return $table; + } + + /** + * Gets schema manager + * + * @return SchemaManager|null + */ + public function getSchemaManager() + { + return $this->schemaManager; + } + + /** + * Set application container + * + * @param $container + */ + public static function setContainer($container) + { + static::$container = $container; + } + + /** + * @return Container + */ + public static function getContainer() + { + return static::$container; + } + + public static function setHookEmitter($emitter) + { + static::$emitter = $emitter; + } + + public function runHook($name, $args = null) + { + if (static::$emitter) { + static::$emitter->execute($name, $args); + } + } + + /** + * Apply a list of hook against the given data + * + * @param array $names + * @param null $data + * @param array $attributes + * + * @return array|\ArrayObject|null + */ + public function applyHooks(array $names, $data = null, array $attributes = []) + { + foreach ($names as $name) { + $data = $this->applyHook($name, $data, $attributes); + } + + return $data; + } + + /** + * Apply hook against the given data + * + * @param $name + * @param null $data + * @param array $attributes + * + * @return \ArrayObject|array|null + */ + public function applyHook($name, $data = null, array $attributes = []) + { + // TODO: Ability to run multiple hook names + // $this->applyHook('hook1,hook2'); + // $this->applyHook(['hook1', 'hook2']); + // ---------------------------------------------------------------------------- + // TODO: Move this to a separate class to handle common events + // $this->applyNewRecord($table, $record); + if (static::$emitter && static::$emitter->hasFilterListeners($name)) { + $isResultSet = $data instanceof ResultSetInterface; + $resultSet = null; + + if ($isResultSet) { + $resultSet = $data; + $data = $resultSet->toArray(); + } + + $data = static::$emitter->apply($name, $data, $attributes); + + if ($isResultSet && $resultSet) { + $data = new \ArrayObject($data); + $resultSet->initialize($data->getIterator()); + $data = $resultSet; + } + } + + return $data; + } + + /** + * Run before table update hooks and filters + * + * @param string $updateCollectionName + * @param array $updateData + * + * @return array|\ArrayObject + */ + protected function runBeforeUpdateHooks($updateCollectionName, $updateData) + { + // Filters + $updateData = $this->applyHook('collection.update:before', $updateData, [ + 'collection_name' => $updateCollectionName + ]); + $updateData = $this->applyHook('collection.update.' . $updateCollectionName . ':before', $updateData); + + // Hooks + $this->runHook('collection.update:before', [$updateCollectionName, $updateData]); + $this->runHook('collection.update.' . $updateCollectionName . ':before', [$updateData]); + + return $updateData; + } + + /** + * Run after table update hooks and filters + * + * @param string $updateTable + * @param string $updateData + */ + protected function runAfterUpdateHooks($updateTable, $updateData) + { + $this->runHook('collection.update', [$updateTable, $updateData]); + $this->runHook('collection.update:after', [$updateTable, $updateData]); + $this->runHook('collection.update.' . $updateTable, [$updateData]); + $this->runHook('collection.update.' . $updateTable . ':after', [$updateData]); + } + + /** + * Gets Directus settings (from DB) + * + * @param null|string $scope + * @param null|string $key + * + * @return mixed + */ + public function getSettings($scope, $key = null) + { + $settings = []; + + if (!static::$container) { + return $settings; + } + + if ($key !== null) { + $settings = get_directus_setting($scope, $key); + } else { + $settings = get_kv_directus_settings($scope); + } + + return $settings; + } + + /** + * Get the table statuses + * + * @return array + */ + public function getAllStatuses() + { + $statuses = []; + $statusMapping = $this->getStatusMapping(); + + if ($statusMapping) { + $statuses = $statusMapping->getAllStatusesValue(); + } + + return $statuses; + } + + /** + * Gets the table published statuses + * + * @return array + */ + public function getPublishedStatuses() + { + return $this->getStatuses('published'); + } + + /** + * Gets the table non-soft-delete statuses + * + * @return array + */ + public function getNonSoftDeleteStatuses() + { + return $this->getStatuses('non-soft-delete'); + } + + /** + * Gets the table statuses with the given type + * + * @param $type + * + * @return array + */ + protected function getStatuses($type) + { + $statuses = []; + $statusMapping = $this->getStatusMapping(); + + if ($statusMapping) { + switch ($type) { + case 'published': + $statuses = $statusMapping->getPublishedStatusesValue(); + break; + case 'non-soft-delete': + $statuses = $statusMapping->getNonSoftDeleteStatusesValue(); + break; + } + } + + return $statuses; + } + + /** + * Gets the collection status mapping + * + * @return StatusMapping|null + * + * @throws CollectionHasNotStatusInterfaceException + * @throws Exception + */ + protected function getStatusMapping() + { + if (!$this->getTableSchema()->hasStatusField()) { + throw new CollectionHasNotStatusInterfaceException($this->table); + } + + $collectionStatusMapping = $this->getTableSchema()->getStatusMapping(); + if (!$collectionStatusMapping) { + if (!static::$container) { + throw new Exception('collection status interface is missing status mapping and the system was unable to find the global status mapping'); + } + + $collectionStatusMapping = static::$container->get('status_mapping'); + } + + $this->validateStatusMapping($collectionStatusMapping); + + return $collectionStatusMapping; + } + + /** + * Validates a status mapping against the field type + * + * @param StatusMapping $statusMapping + * + * @throws CollectionHasNotStatusInterfaceException + * @throws StatusMappingEmptyException + * @throws StatusMappingWrongValueTypeException + */ + protected function validateStatusMapping(StatusMapping $statusMapping) + { + if ($statusMapping->isEmpty()) { + throw new StatusMappingEmptyException($this->table); + } + + $statusField = $this->getTableSchema()->getStatusField(); + if (!$statusField) { + throw new CollectionHasNotStatusInterfaceException($this->table); + } + + $type = 'string'; + if (DataTypes::isNumericType($statusField->getOriginalType())) { + $type = 'numeric'; + } + + foreach ($statusMapping as $status) { + if (!call_user_func('is_' . $type, $status->getValue())) { + throw new StatusMappingWrongValueTypeException($type, $statusField->getName(), $this->table); + } + } + } +} diff --git a/src/core/Directus/Database/TableGateway/DirectusActivityTableGateway.php b/src/core/Directus/Database/TableGateway/DirectusActivityTableGateway.php new file mode 100644 index 0000000000..c98625c5b1 --- /dev/null +++ b/src/core/Directus/Database/TableGateway/DirectusActivityTableGateway.php @@ -0,0 +1,221 @@ + 'DESC']; + $params = $this->applyDefaultEntriesSelectParams($params); + $builder = new Builder($this->getAdapter()); + $builder->from($this->getTable()); + + // TODO: Move this to applyDefaultEntriesSelectParams method + $tableSchema = $this->getTableSchema(); + $columns = SchemaService::getAllCollectionFieldsName($tableSchema->getName()); + if (ArrayUtils::has($params, 'columns')) { + $columns = ArrayUtils::get($params, 'columns'); + } + + $builder->columns($columns); + $hasActiveColumn = $tableSchema->hasStatusColumn(); + + $builder = $this->applyParamsToTableEntriesSelect($params, $builder, $tableSchema, $hasActiveColumn); + $select = $builder->buildSelect(); + + $select + ->where + ->nest + ->isNull('parent_id') + ->OR + ->equalTo('type', 'FILES') + ->unnest; + + $rowset = $this->selectWith($select); + $rowset = $rowset->toArray(); + + $countTotalWhere = new Where; + $countTotalWhere + ->isNull('parent_id') + ->OR + ->equalTo('type', 'FILES'); + + return $this->wrapData($this->parseRecord($rowset), false, ArrayUtils::get($params, 'meta', 0)); + } + + public function recordLogin($userId) + { + $logData = [ + 'type' => self::TYPE_LOGIN, + 'collection' => 'directus_users', + 'action' => self::ACTION_LOGIN, + 'user' => $userId, + 'item' => $userId, + 'datetime' => DateTimeUtils::nowInUTC()->toString(), + 'ip' => get_request_ip(), + 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '' + ]; + + $insert = new Insert($this->getTable()); + $insert + ->values($logData); + + $this->insertWith($insert); + } + + /** + * Get the last update date from a list of row ids in the given table + * + * @param string $table + * @param mixed $ids + * @param array $params + * + * @return array|null + */ + public function getLastUpdated($table, $ids, array $params = []) + { + if (!is_array($ids)) { + $ids = [$ids]; + } + + $sql = new Sql($this->adapter); + $select = $sql->select($this->getTable()); + + $select->columns([ + 'row_id', + 'user', + 'datetime' => new Expression('MAX(datetime)') + ]); + + $select->where([ + 'table_name' => $table, + 'type' => 'ENTRY', + new In('action', ['UPDATE', 'ADD']), + new In('row_id', $ids) + ]); + + $select->group(['row_id', 'user']); + $select->order(['datetime' => 'DESC']); + + $statement = $this->sql->prepareStatementForSqlObject($select); + $result = iterator_to_array($statement->execute()); + + return $this->wrapData($this->parseRecord($result), false, ArrayUtils::get($params, 'meta', 0)); + } + + public function getMetadata($table, $id) + { + $sql = new Sql($this->adapter); + $select = $sql->select($this->getTable()); + + $select->columns([ + 'action', + 'user', + 'datetime' => new Expression('MAX(datetime)') + ]); + + $on = 'directus_users.id = directus_activity.user'; + $select->join('directus_users', $on, []); + + $select->where([ + 'table_name' => $table, + 'row_id' => $id, + 'type' => $table === 'directus_files' ? static::TYPE_FILES : static::TYPE_ENTRY, + new In('action', ['ADD', 'UPDATE']) + ]); + + $select->group([ + 'action', + 'user' + ]); + + $select->limit(2); + + $statement = $this->sql->prepareStatementForSqlObject($select); + $result = iterator_to_array($statement->execute()); + $result = $this->parseRecord($result); + + $data = [ + 'created_on' => null, + 'created_by' => null, + 'updated_on' => null, + 'updated_by' => null + ]; + + foreach ($result as $row) { + switch (ArrayUtils::get($row, 'action')) { + case static::ACTION_ADD: + $data['created_by'] = $row['user']; + $data['created_on'] = $row['datetime']; + break; + case static::ACTION_UPDATE: + $data['updated_by'] = $row['user']; + $data['updated_on'] = $row['datetime']; + break; + } + } + + if (!$data['updated_by'] && !$data['updated_on']) { + $data['updated_on'] = $data['created_on']; + $data['updated_by'] = $data['created_by']; + } + + return $data; + } +} diff --git a/src/core/Directus/Database/TableGateway/DirectusCollectionPresetsTableGateway.php b/src/core/Directus/Database/TableGateway/DirectusCollectionPresetsTableGateway.php new file mode 100644 index 0000000000..e8c77bf406 --- /dev/null +++ b/src/core/Directus/Database/TableGateway/DirectusCollectionPresetsTableGateway.php @@ -0,0 +1,364 @@ + 'id', + 'sort_order' => 'ASC', + 'status' => '1,2', + 'title' => null + ]; + + public static $defaultPreferencesValuesByTable = [ + 'directus_files' => [ + 'sort' => 'date_uploaded', + 'sort_order' => 'DESC', + 'columns_visible' => 'name,title,caption,type,size,user,date_uploaded' + ] + ]; + + public function applyDefaultPreferences($table, $preferences) + { + // Table-specific default values + if (array_key_exists($table, self::$defaultPreferencesValuesByTable)) { + $tableDefaultPreferences = self::$defaultPreferencesValuesByTable[$table]; + foreach ($tableDefaultPreferences as $field => $defaultValue) { + if (!isset($preferences[$field])) { + $preferences[$field] = $defaultValue; + } + } + } + + // Global default values + $primaryKeyFieldName = SchemaService::getCollectionPrimaryKey($table); + if ($primaryKeyFieldName) { + self::$defaultPreferencesValues['sort'] = $primaryKeyFieldName; + } + + foreach (self::$defaultPreferencesValues as $field => $defaultValue) { + if (!isset($preferences[$field]) || ('0' !== $preferences[$field] && empty($preferences[$field]))) { + if (!isset($preferences[$field])) { + $preferences[$field] = $defaultValue; + } + } + } + + if (isset($preferences['sort'])) { + if (!SchemaService::hasCollectionSortField($table)) { + $preferences['sort'] = SchemaService::getCollectionSortField($table); + } + } + + return $preferences; + } + + public function constructPreferences($user_id, $table, $preferences = null, $title = null) + { + if ($preferences) { + $newPreferencesData = false; + + // @todo enforce non-empty set + if (empty($preferences['columns_visible'])) { + $newPreferencesData = true; + $columns_visible = SchemaService::getCollectionFields($table, 6); + $preferences['columns_visible'] = implode(',', $columns_visible); + } + + $preferencesDefaultsApplied = $this->applyDefaultPreferences($table, $preferences); + if (count(array_diff($preferences, $preferencesDefaultsApplied))) { + $newPreferencesData = true; + } + $preferences = $preferencesDefaultsApplied; + if ($newPreferencesData) { + $id = $this->addOrUpdateRecordByArray($preferences); + } + return $preferences; + } + + $insert = new Insert($this->table); + + // User doesn't have any preferences for this table yet. Please create! + $columns_visible = SchemaService::getCollectionFields($table, 6); + $data = [ + 'user' => $user_id, + 'columns_visible' => implode(',', $columns_visible), + 'table_name' => $table, + 'title' => $title + ]; + + if (SchemaService::hasCollectionSortField($table)) { + $data['sort'] = SchemaService::getCollectionSortField($table); + } + + $data = $this->applyDefaultPreferences($table, $data); + + $insert + ->values($data); + + $this->insertWith($insert); + + return $data; + } + + public function fetchByUserAndTableAndTitle($user_id, $table, $title = null, array $columns = []) + { + $select = new Select($this->table); + + if (!empty($columns)) { + $select->columns(array_merge([$this->primaryKeyFieldName], $columns)); + } + + $select->limit(1); + $select + ->where + ->equalTo('table_name', $table) + ->equalTo('user', $user_id); + + if ($title) { + $select->where->equalTo('title', $title); + } else { + $select->where->isNull('title'); + } + + $preferences = $this + ->selectWith($select) + ->current(); + + if ($preferences) { + $preferences = $preferences->toArray(); + } + + if ($preferences) { + $preferences = $this->constructPreferences($user_id, $table, $preferences); + } + + if ($preferences && !empty($columns)) { + $preferences = ArrayUtils::pick($preferences, $columns); + } + + return $this->parseRecord($preferences); + } + + /** + * @deprecated + * @param $user_id + * @param $title + * @return array + */ + public function fetchByUserAndTitle($user_id, $title) + { + $result = $this->fetchEntityByUserAndTitle($user_id, $title); + return (isset($result['data'])) ? $result['data'] : []; + + } + + /** + * @param int $user_id + * @param string $title + * @param array $params + * + * @return array|mixed + */ + public function fetchEntityByUserAndTitle($user_id, $title, array $params = []) + { + // TODO: Merge with fetchByUserAndTableAndTitle + $fields = ArrayUtils::get($params, 'fields'); + if (!empty($fields)) { + if (!is_array($fields)) { + $fields = StringUtils::csv($fields); + } + + $params['fields'] = array_merge(['table_name'], $fields); + } + + $result = $this->fetchItems(array_merge($params, [ + 'single' => true, + 'filters' => [ + 'user' => $user_id, + 'title' => $title + ] + ])); + + $result = $result + ? $this->constructPreferences($user_id, $result['table_name'], $result) + : []; + + if (!empty($fields)) { + $result = ArrayUtils::pick($result, $fields); + } + + return ['data' => $result]; + } + + /* + * Temporary while I figured out why the method above + * doesn't not construct preferences on table without preferences. + */ + public function fetchByUserAndTable($user_id, $table, array $columns = []) + { + $select = new Select($this->table); + + if (!empty($columns)) { + $select->columns([$this->primaryKeyFieldName], $columns); + } + + $select->limit(1); + $select + ->where + ->equalTo('table_name', $table) + ->equalTo('user', $user_id); + + $preferences = $this + ->selectWith($select) + ->current(); + + if (!$preferences) { + return $this->constructPreferences($user_id, $table); + } + + if ($preferences) { + $preferences = $preferences->toArray(); + } + + if ($preferences && !empty($columns)) { + $preferences = ArrayUtils::pick($preferences, $columns); + } + + return $this->parseRecord($preferences); + } + + public function updateDefaultByName($user_id, $table, $data) + { + $update = new Update($this->table); + unset($data['id']); + unset($data['title']); + unset($data['table_name']); + unset($data['user']); + if (!isset($data) || !is_array($data)) { + $data = []; + } + $update->set($data) + ->where + ->equalTo('table_name', $table) + ->equalTo('user', $user_id) + ->isNull('title'); + $this->updateWith($update); + } + + // @param $assoc return associative array with table_name as keys + public function fetchAllByUser($user_id, $assoc = false) + { + $select = new Select($this->table); + $select->columns([ + 'id', + 'user', + 'table_name', + 'columns_visible', + 'sort', + 'sort_order', + 'status', + 'title', + 'search_string', + 'list_view_options' + ]); + + $select->where->equalTo('user', $user_id) + ->isNull('title'); + + $coreTables = $this->schemaManager->getDirectusTables(static::$IGNORED_TABLES); + + $select->where->addPredicate(new NotIn('table_name', $coreTables)); + $metadata = new \Zend\Db\Metadata\Metadata($this->getAdapter()); + + $tables = $metadata->getTableNames(); + + $tables = array_diff($tables, $coreTables); + + $rows = $this->selectWith($select)->toArray(); + + $preferences = []; + $tablePrefs = []; + + foreach ($rows as $row) { + $tablePrefs[$row['table_name']] = $row; + } + + //Get Default Preferences + foreach ($tables as $key => $table) { + // Honor ACL. Skip the tables that the user doesn't have access too + if (!SchemaService::canGroupReadCollection($table)) { + continue; + } + + $tableName = $table; + + if (!isset($tablePrefs[$table])) { + $table = null; + } else { + $table = $tablePrefs[$table]; + } + + if (!isset($table['user'])) { + $table = null; + } + + $table = $this->constructPreferences($user_id, $tableName, $table); + $preferences[$tableName] = $table; + } + + return $preferences; + } + + public function fetchSavedPreferencesByUserAndTable($user_id, $table) + { + $select = new Select($this->table); + $select + ->where + ->equalTo('table_name', $table) + ->equalTo('user', $user_id) + ->isNotNull('title'); + + $preferences = $this + ->selectWith($select); + + if ($preferences) { + $preferences = $preferences->toArray(); + } + + return $preferences; + } +} diff --git a/src/core/Directus/Database/TableGateway/DirectusCollectionsTableGateway.php b/src/core/Directus/Database/TableGateway/DirectusCollectionsTableGateway.php new file mode 100644 index 0000000000..bdab7d217d --- /dev/null +++ b/src/core/Directus/Database/TableGateway/DirectusCollectionsTableGateway.php @@ -0,0 +1,18 @@ + $this->table]); + + $subSelect = new Select(['ur' => 'directus_user_roles']); + $subSelect->where->equalTo('user', $userId); + $subSelect->limit(1); + + $select->join( + ['ur' => $subSelect], + 'p.role = ur.role', + [ + 'user_role' => 'role' + ], + $select::JOIN_RIGHT + ); + + $select->where->equalTo('ur.user', $userId); + + $statement = $this->sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); + + $permissionsByCollection = []; + foreach ($result as $permission) { + foreach ($permission as $field => &$value) { + if (in_array($field, ['read_field_blacklist', 'write_field_blacklist'])) { + $value = explode(',', $value); + } + } + + ArrayUtils::rename($permission, 'user_role', 'role'); + $permissionsByCollection[$permission['collection']][] = $this->parseRecord($permission); + } + + return $permissionsByCollection; + } + + // @TODO: move it to another object. + private function isCurrentUserAdmin() + { + if (!$this->acl) { + return true; + } + + //Dont let non-admins have alter privilege + return ($this->acl->getGroupId() == 1) ? true : false; + } + + private function verifyPrivilege($attributes) + { + // Making sure alter is set for admin only. + if (array_key_exists('allow_alter', $attributes)) { + if ($this->isCurrentUserAdmin()) { + $attributes['allow_alter'] = 1; + } else { + $attributes['allow_alter'] = 0; + } + } + + return $attributes; + } + + /** + * Get Permissions for the given Group ID + * @param $groupId + * + * @return array + */ + public function getGroupPrivileges($groupId) + { + return $this->fetchGroupPrivileges($groupId); + } + + public function fetchGroupPrivileges($groupId, $statusId = false) + { + $select = new Select($this->table); + $select->where->equalTo('group', $groupId); + + if ($statusId !== false) { + if ($statusId === null) { + $select->where->isNull('status'); + } else { + $select->where->equalTo('status', $statusId); + } + } + + $rowset = $this->selectWith($select); + $rowset = $rowset->toArray(); + + $privilegesByTable = []; + foreach ($rowset as $row) { + foreach ($row as $field => &$value) { + if (in_array($field, ['read_field_blacklist', 'write_field_blacklist'])) { + $value = explode(',', $value); + } + } + + $privilegesByTable[$row['collection']][] = $this->parseRecord($row); + } + + return $privilegesByTable; + } + + public function fetchById($privilegeId) + { + $select = new Select($this->table); + $select->where->equalTo('id', $privilegeId); + $rowset = $this->selectWith($select); + $rowset = $rowset->toArray(); + + return $this->parseRecord(current($rowset)); + } + + // @todo This currently only supports permissions, + // include blacklists when there is a UI for it + public function insertPrivilege($attributes) + { + $attributes = $this->verifyPrivilege($attributes); + // @todo: this should fallback on field default value + if (!isset($attributes['status_id'])) { + $attributes['status_id'] = NULL; + } + + $attributes = $this->getFillableFields($attributes); + + $insert = new Insert($this->getTable()); + $insert + ->columns(array_keys($attributes)) + ->values($attributes); + $this->insertWith($insert); + + $privilegeId = $this->lastInsertValue; + + return $this->fetchById($privilegeId); + } + + public function getFillableFields($attributes) + { + return $data = array_intersect_key($attributes, array_flip($this->fillable)); + } + + // @todo This currently only supports permissions, + // include blacklists when there is a UI for it + public function updatePrivilege($attributes) + { + $attributes = $this->verifyPrivilege($attributes); + + $data = $this->getFillableFields($attributes); + + $update = new Update($this->getTable()); + $update->where->equalTo('id', $attributes['id']); + $update->set($data); + $this->updateWith($update); + + return $this->fetchById($attributes['id']); + } + + public function fetchPerTable($groupId, $tableName = null, array $columns = []) + { + // Don't include tables that can't have privileges changed + /*$blacklist = array( + 'directus_columns', + 'directus_messages_recipients', + 'directus_preferences', + 'directus_privileges', + 'directus_settings', + 'directus_social_feeds', + 'directus_social_posts', + 'directus_storage_adapters', + 'directus_tab_privileges', + 'directus_tables', + 'directus_ui', + 'directus_users_copy' + );*/ + $blacklist = []; + + + $select = new Select($this->table); + if (!empty($columns)) { + // Force the primary key + // It's going to be removed below + $select->columns(array_merge([$this->primaryKeyFieldName], $columns)); + } + + $select->where->equalTo('group', $groupId); + if (!is_null($tableName)) { + $select->where->equalTo('collection', $tableName); + $select->limit(1); + } + $rowset = $this->selectWith($select); + $rowset = $rowset->toArray(); + + $tableSchema = new SchemaService(); + $tables = $tableSchema->getTablenames(); + $privileges = []; + $privilegesHash = []; + + foreach ($rowset as $item) { + if (in_array($item['collection'], $blacklist)) { + continue; + } + + if (!empty($columns)) { + $item = ArrayUtils::pick($item, $columns); + } + + $privilegesHash[$item['collection']] = $item; + $privileges[] = $item; + } + + foreach ($tables as $table) { + if (in_array($table, $blacklist)) { + continue; + } + + if (array_key_exists($table['name'], $privilegesHash)) { + continue; + } + + if (!is_null($tableName)) { + continue; + } + + $item = ['collection' => $table['name'], 'group' => $groupId, 'status_id' => null]; + + $privileges[] = $item; + } + + // sort ascending + usort($privileges, function ($a, $b) { + return strcmp($a['collection'], $b['collection']); + }); + + $privileges = is_null($tableName) ? $privileges : reset($privileges); + + return $this->parseRecord($privileges); + } + + public function fetchGroupPrivilegesRaw($group_id) + { + $select = new Select($this->table); + $select->where->equalTo('group', $group_id); + $rowset = $this->selectWith($select); + $rowset = $rowset->toArray(); + + return $this->parseRecord($rowset); + } + + public function findByStatus($collection, $group_id, $status_id) + { + $select = new Select($this->table); + $select->where + ->equalTo('collection', $collection) + ->equalTo('group', $group_id) + ->equalTo('status', $status_id); + $rowset = $this->selectWith($select); + $rowset = $rowset->toArray(); + return current($rowset); + } +} diff --git a/src/core/Directus/Database/TableGateway/DirectusRolesTableGateway.php b/src/core/Directus/Database/TableGateway/DirectusRolesTableGateway.php new file mode 100644 index 0000000000..725dc2f587 --- /dev/null +++ b/src/core/Directus/Database/TableGateway/DirectusRolesTableGateway.php @@ -0,0 +1,19 @@ + 20, + 'offset' => 0, + 'search' => null, + 'meta' => 0, + 'status' => null + ]; + + protected $operatorShorthand = [ + 'eq' => ['operator' => 'equal_to', 'not' => false], + '=' => ['operator' => 'equal_to', 'not' => false], + 'neq' => ['operator' => 'equal_to', 'not' => true], + '!=' => ['operator' => 'equal_to', 'not' => true], + '<>' => ['operator' => 'equal_to', 'not' => true], + 'in' => ['operator' => 'in', 'not' => false], + 'nin' => ['operator' => 'in', 'not' => true], + 'lt' => ['operator' => 'less_than', 'not' => false], + 'lte' => ['operator' => 'less_than_or_equal', 'not' => false], + 'gt' => ['operator' => 'greater_than', 'not' => false], + 'gte' => ['operator' => 'greater_than_or_equal', 'not' => false], + + 'nlike' => ['operator' => 'like', 'not' => true], + 'contains' => ['operator' => 'like'], + 'ncontains' => ['operator' => 'like', 'not' => true], + + '<' => ['operator' => 'less_than', 'not' => false], + '<=' => ['operator' => 'less_than_or_equal', 'not' => false], + '>' => ['operator' => 'greater_than', 'not' => false], + '>=' => ['operator' => 'greater_than_or_equal', 'not' => false], + + 'nnull' => ['operator' => 'null', 'not' => true], + + 'nempty' => ['operator' => 'empty', 'not' => true], + + 'nhas' => ['operator' => 'has', 'not' => true], + + 'nbetween' => ['operator' => 'between', 'not' => true], + ]; + + public function deleteRecord($id, array $params = []) + { + // TODO: Add "item" hook, different from "table" hook + $success = $this->delete([ + $this->primaryKeyFieldName => $id + ]); + + if (!$success) { + throw new ErrorException( + sprintf('Error deleting a record in %s with id %s', $this->table, $id) + ); + } + + if ($this->table !== SchemaManager::COLLECTION_ACTIVITY) { + $parentLogEntry = BaseRowGateway::makeRowGatewayFromTableName('id', 'directus_activity', $this->adapter); + $logData = [ + 'type' => DirectusActivityTableGateway::makeLogTypeFromTableName($this->table), + 'action' => DirectusActivityTableGateway::ACTION_DELETE, + 'user' => $this->acl->getUserId(), + 'datetime' => DateTimeUtils::nowInUTC()->toString(), + 'ip' => get_request_ip(), + 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', + 'collection' => $this->table, + 'item' => $id, + 'comment' => ArrayUtils::get($params, 'activity_comment') + ]; + $parentLogEntry->populate($logData, false); + $parentLogEntry->save(); + } + } + + /** + * @param array $data + * @param array $params + * + * @return BaseRowGateway + */ + public function updateRecord($data, array $params = []) + { + return $this->manageRecordUpdate($this->getTable(), $data, $params); + } + + /** + * @param $data + * @param array $params + * + * @return BaseRowGateway + */ + public function revertRecord($data, array $params = []) + { + return $this->updateRecord($data, array_merge($params, ['revert' => true])); + } + + /** + * @param string $tableName + * @param array $recordData + * @param array $params + * @param null $childLogEntries + * @param bool $parentCollectionRelationshipsChanged + * @param array $parentData + * + * @return BaseRowGateway + */ + public function manageRecordUpdate($tableName, $recordData, array $params = [], &$childLogEntries = null, &$parentCollectionRelationshipsChanged = false, $parentData = []) + { + $TableGateway = $this; + if ($tableName !== $this->getTable()) { + $TableGateway = new RelationalTableGateway($tableName, $this->adapter, $this->acl); + } + + $activityEntryMode = ArrayUtils::get($params, 'activity_mode', static::ACTIVITY_ENTRY_MODE_PARENT); + $recordIsNew = !array_key_exists($TableGateway->primaryKeyFieldName, $recordData); + + $tableSchema = SchemaService::getCollection($tableName); + + $currentUserId = $this->acl ? $this->acl->getUserId() : null; + $isAdmin = $this->acl ? $this->acl->isAdmin() : false; + + // Do not let non-admins make admins + // TODO: Move to hooks + if ($tableName == 'directus_users' && !$isAdmin) { + if (isset($recordData['group']) && $recordData['group']['id'] == 1) { + unset($recordData['group']); + } + } + + $thisIsNested = ($activityEntryMode == self::ACTIVITY_ENTRY_MODE_CHILD); + + // Recursive functions will change this value (by reference) as necessary + // $nestedCollectionRelationshipsChanged = $thisIsNested ? $parentCollectionRelationshipsChanged : false; + $nestedCollectionRelationshipsChanged = false; + if ($thisIsNested) { + $nestedCollectionRelationshipsChanged = &$parentCollectionRelationshipsChanged; + } + + // Recursive functions will append to this array by reference + // $nestedLogEntries = $thisIsNested ? $childLogEntries : []; + $nestedLogEntries = []; + if ($thisIsNested) { + $nestedLogEntries = &$childLogEntries; + } + + // Update and/or Add Many-to-One Associations + $recordData = $TableGateway->addOrUpdateManyToOneRelationships($tableSchema, $recordData, $nestedLogEntries, $nestedCollectionRelationshipsChanged); + + $parentRecordWithoutAlias = []; + foreach ($recordData as $key => $data) { + $column = $tableSchema->getField($key); + + // TODO: To work with files + // As `data` is not set as alias for files we are checking for actual aliases + if ($column && $column->isAlias()) { + continue; + } + + $parentRecordWithoutAlias[$key] = $data; + } + + // NOTE: set the primary key to null + // to default the value to whatever increment value is next + // avoiding the error of inserting nothing + if (empty($parentRecordWithoutAlias)) { + $parentRecordWithoutAlias[$tableSchema->getPrimaryKeyName()] = null; + } + + // If more than the record ID is present. + $newRecordObject = null; + $parentRecordChanged = $this->recordDataContainsNonPrimaryKeyData($recordData); + + if ($parentRecordChanged) { + // Update the parent row, w/ any new association fields replaced by their IDs + $newRecordObject = $TableGateway + ->addOrUpdateRecordByArray($parentRecordWithoutAlias); + if (!$newRecordObject) { + return []; + } + + if ($newRecordObject) { + $newRecordObject = $newRecordObject->toArray(); + } + } + + // Do it this way, because & byref for outcome of ternary operator spells trouble + $draftRecord = &$parentRecordWithoutAlias; + if ($recordIsNew) { + $draftRecord = &$newRecordObject; + } + + // Restore X2M relationship / alias fields to the record representation & process these relationships. + $collectionColumns = $tableSchema->getAliasFields(); + foreach ($collectionColumns as $collectionColumn) { + $colName = $collectionColumn->getName(); + if (isset($recordData[$colName])) { + $draftRecord[$colName] = $recordData[$colName]; + } + } + + // parent + if ($activityEntryMode === self::ACTIVITY_ENTRY_MODE_PARENT) { + $parentData = [ + 'item' => array_key_exists($this->primaryKeyFieldName, $recordData) ? $recordData[$this->primaryKeyFieldName] : null, + 'collection' => $tableName + ]; + } + + $draftRecord = $TableGateway->addOrUpdateToManyRelationships($tableSchema, $draftRecord, $nestedLogEntries, $nestedCollectionRelationshipsChanged, $parentData); + $rowId = $draftRecord[$this->primaryKeyFieldName]; + + $columnNames = SchemaService::getAllNonAliasCollectionFieldNames($tableName); + $TemporaryTableGateway = new TableGateway($tableName, $this->adapter); + $fullRecordData = $TemporaryTableGateway->select(function ($select) use ($rowId, $columnNames) { + $select->where->equalTo($this->primaryKeyFieldName, $rowId); + $select->limit(1)->columns($columnNames); + })->current(); + + if (!$fullRecordData) { + $recordType = $recordIsNew ? 'new' : 'pre-existing'; + throw new \RuntimeException('Attempted to load ' . $recordType . ' record post-insert with empty result. Lookup via row id: ' . print_r($rowId, true)); + } + + $fullRecordData = (array) $fullRecordData; + if ($recordIsNew) { + $deltaRecordData = $parentRecordWithoutAlias; + } else { + $deltaRecordData = array_intersect_key( + ArrayUtils::omit((array)$parentRecordWithoutAlias, $this->primaryKeyFieldName), + $fullRecordData + ); + } + + $statusField = $tableSchema->getStatusField(); + if ($recordIsNew) { + $logEntryAction = DirectusActivityTableGateway::ACTION_ADD; + } else if (ArrayUtils::get($params, 'revert') === true) { + $logEntryAction = DirectusActivityTableGateway::ACTION_REVERT; + } else { + $logEntryAction = DirectusActivityTableGateway::ACTION_UPDATE; + + try { + if ( + $statusField + && ArrayUtils::has($deltaRecordData, $statusField->getName()) + && in_array( + ArrayUtils::get($deltaRecordData, $tableSchema->getStatusField()->getName()), + $this->getStatusMapping()->getSoftDeleteStatusesValue() + ) + ) { + $logEntryAction = DirectusActivityTableGateway::ACTION_SOFT_DELETE; + } + } catch (\Exception $e) { + // the field doesn't have a status mapping + } + } + + if ($this->getTable() != SchemaManager::COLLECTION_ACTIVITY) { + switch ($activityEntryMode) { + // Activity logging is enabled, and I am a nested action + case self::ACTIVITY_ENTRY_MODE_CHILD: + $childLogEntries[] = [ + 'type' => DirectusActivityTableGateway::makeLogTypeFromTableName($this->table), + 'action' => $logEntryAction, + 'user' => $currentUserId, + 'datetime' => DateTimeUtils::nowInUTC()->toString(), + 'ip' => get_request_ip(), + 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', + 'collection' => $tableName, + 'parent_item' => isset($parentData['item']) ? $parentData['item'] : null, + 'parent_collection' => isset($parentData['collection']) ? $parentData['collection'] : null, + 'data' => json_encode($fullRecordData), + 'delta' => !empty($deltaRecordData) ? json_encode($deltaRecordData) : null, + 'parent_changed' => boolval($parentRecordChanged), + 'item' => $rowId, + 'comment' => null + ]; + if ($recordIsNew) { + /** + * This is a nested call, creating a new record w/in a foreign collection. + * Indicate by reference that the top-level record's relationships have changed. + */ + $parentCollectionRelationshipsChanged = true; + } + break; + + case self::ACTIVITY_ENTRY_MODE_PARENT: + // Does this act deserve a log? + $parentRecordNeedsLog = $nestedCollectionRelationshipsChanged || $parentRecordChanged; + /** + * NESTED QUESTIONS! + * @todo what do we do if the foreign record OF a foreign record changes? + * is that activity entry also directed towards this parent activity entry? + * @todo how should nested activity entries relate to the revision histories of foreign items? + * @todo one day: treat children as parents if this top-level record was not modified. + */ + // Produce log if something changed. + if ($parentRecordChanged || $nestedCollectionRelationshipsChanged) { + // Save parent log entry + $parentLogEntry = BaseRowGateway::makeRowGatewayFromTableName('id', 'directus_activity', $this->adapter); + $logData = [ + 'type' => DirectusActivityTableGateway::makeLogTypeFromTableName($this->table), + 'action' => $logEntryAction, + 'user' => $currentUserId, + 'datetime' => DateTimeUtils::nowInUTC()->toString(), + 'ip' => get_request_ip(), + 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', + 'collection' => $tableName, + 'item' => $rowId, + 'comment' => ArrayUtils::get($params, 'activity_comment') + ]; + $parentLogEntry->populate($logData, false); + $parentLogEntry->save(); + + // Add Revisions + $revisionTableGateway = new RelationalTableGateway(SchemaManager::COLLECTION_REVISIONS, $this->adapter); + $revisionTableGateway->insert([ + 'activity' => $parentLogEntry->getId(), + 'collection' => $tableName, + 'item' => $rowId, + 'data' => json_encode($fullRecordData), + 'delta' => !empty($deltaRecordData) ? json_encode($deltaRecordData) : null, + 'parent_item' => null, + 'parent_collection' => null, + 'parent_changed' => null,//boolval($parentRecordChanged) + ]); + + // Update & insert nested activity entries + $ActivityGateway = new DirectusActivityTableGateway($this->adapter); + foreach ($nestedLogEntries as $entry) { + // TODO: ought to insert these in one batch + $ActivityGateway->insert(ArrayUtils::omit($entry, [ + 'parent_item', + 'parent_collection', + 'data', + 'delta', + 'parent_changed', + ])); + $revisionTableGateway->insert([ + 'activity' => $ActivityGateway->lastInsertValue, + 'collection' => ArrayUtils::get($entry, 'collection'), + 'item' => ArrayUtils::get($entry, 'item'), + 'data' => ArrayUtils::get($entry, 'data'), + 'delta' => ArrayUtils::get($entry, 'delta'), + 'parent_item' => ArrayUtils::get($entry, 'parent_item'), + 'parent_collection' => ArrayUtils::get($entry, 'parent_collection'), + 'parent_changed' => ArrayUtils::get($entry, 'parent_changed') + ]); + } + } + break; + } + } + + // Yield record object + $recordGateway = new BaseRowGateway($TableGateway->primaryKeyFieldName, $tableName, $this->adapter, $this->acl); + $recordGateway->populate($this->parseRecord($fullRecordData), true); + + return $recordGateway; + } + + /** + * @param Collection $schema The table schema array. + * @param array $parentRow The parent record being updated. + * @return array + */ + public function addOrUpdateManyToOneRelationships($schema, $parentRow, &$childLogEntries = null, &$parentCollectionRelationshipsChanged = false) + { + // Create foreign row and update local column with the data id + foreach ($schema->getFields() as $field) { + $fieldName = $field->getName(); + + if (!$field->isManyToOne()) { + continue; + } + + // Ignore absent values & non-arrays + if (!isset($parentRow[$fieldName]) || !is_array($parentRow[$fieldName])) { + continue; + } + + // Ignore non-arrays and empty collections + if (empty($parentRow[$fieldName])) { + // Once they're managed, remove the foreign collections from the record array + unset($parentRow[$fieldName]); + continue; + } + + $foreignDataSet = $parentRow[$fieldName]; + $foreignRow = $foreignDataSet; + $foreignTableName = $field->getRelationship()->getCollectionB(); + $foreignTableSchema = $this->getTableSchema($foreignTableName); + $primaryKey = $foreignTableSchema->getPrimaryKeyName(); + $ForeignTable = new RelationalTableGateway($foreignTableName, $this->adapter, $this->acl); + + if ($primaryKey && ArrayUtils::get($foreignRow, $this->deleteFlag) === true) { + $Where = new Where(); + $Where->equalTo($primaryKey, $foreignRow[$primaryKey]); + $ForeignTable->delete($Where); + + $parentRow[$fieldName] = $field->isNullable() ? null : 0; + + continue; + } + + // Update/Add foreign record + if ($this->recordDataContainsNonPrimaryKeyData($foreignRow, $foreignTableSchema->getPrimaryKeyName())) { + // NOTE: using manageRecordUpdate instead of addOrUpdateRecordByArray to update related data + $foreignRow = $this->manageRecordUpdate($foreignTableName, $foreignRow); + } + + $parentRow[$fieldName] = $foreignRow[$primaryKey]; + } + + return $parentRow; + } + + /** + * @param Collection $schema The table schema array. + * @param array $parentRow The parent record being updated. + * @return array + */ + public function addOrUpdateToManyRelationships($schema, $parentRow, &$childLogEntries = null, &$parentCollectionRelationshipsChanged = false, $parentData = []) + { + // Create foreign row and update local column with the data id + foreach ($schema->getFields() as $field) { + $fieldName = $field->getName(); + + if (!$field->hasRelationship()) { + continue; + } + + // Ignore absent values & non-arrays + if (!isset($parentRow[$fieldName]) || !is_array($parentRow[$fieldName])) { + continue; + } + + $relationship = $field->getRelationship(); + $fieldIsCollectionAssociation = $relationship->isToMany(); + + // Ignore non-arrays and empty collections + if (empty($parentRow[$fieldName])) {//} || ($fieldIsOneToMany && )) { + // Once they're managed, remove the foreign collections from the record array + unset($parentRow[$fieldName]); + continue; + } + + $foreignDataSet = $parentRow[$fieldName]; + + /** One-to-Many, Many-to-Many */ + if ($fieldIsCollectionAssociation) { + $this->enforceColumnHasNonNullValues($relationship->toArray(), ['collection_b', 'field_a'], $this->table); + $foreignTableName = $relationship->getCollectionB(); + $foreignJoinColumn = $relationship->getFieldB(); + switch ($relationship->getType()) { + /** One-to-Many */ + case FieldRelationship::ONE_TO_MANY: + $ForeignTable = new RelationalTableGateway($foreignTableName, $this->adapter, $this->acl); + foreach ($foreignDataSet as &$foreignRecord) { + if (empty($foreignRecord)) { + continue; + } + + // TODO: Fix a bug when fetching a single column + // before fetching all columns from a table + // due to our basic "cache" implementation on schema layer + $hasPrimaryKey = isset($foreignRecord[$ForeignTable->primaryKeyFieldName]); + + if ($hasPrimaryKey && ArrayUtils::get($foreignRecord, $this->deleteFlag) === true) { + $Where = new Where(); + $Where->equalTo($ForeignTable->primaryKeyFieldName, $foreignRecord[$ForeignTable->primaryKeyFieldName]); + $ForeignTable->delete($Where); + + continue; + } + + // only add parent id's to items that are lacking the parent column + if (!array_key_exists($foreignJoinColumn, $foreignRecord)) { + $foreignRecord[$foreignJoinColumn] = $parentRow['id']; + } + + $foreignRecord = $this->manageRecordUpdate( + $foreignTableName, + $foreignRecord, + ['activity_mode' => self::ACTIVITY_ENTRY_MODE_CHILD], + $childLogEntries, + $parentCollectionRelationshipsChanged, + $parentData + ); + } + break; + + /** Many-to-Many */ + case FieldRelationship::MANY_TO_MANY: + $foreignJoinColumn = $relationship->getJunctionKeyB(); + /** + * [+] Many-to-Many payloads declare collection items this way: + * $parentRecord['collectionName1'][0-9]['data']; // record key-value array + * [+] With optional association metadata: + * $parentRecord['collectionName1'][0-9]['id']; // for updating a pre-existing junction row + * $parentRecord['collectionName1'][0-9]['active']; // for disassociating a junction via the '0' value + */ + + $this->enforceColumnHasNonNullValues($relationship->toArray(), ['junction_collection', 'junction_key_a'], $this->table); + $junctionTableName = $relationship->getJunctionCollection();//$column['relationship']['junction_table']; + $junctionKeyLeft = $relationship->getJunctionKeyA();//$column['relationship']['junction_key_left']; + $junctionKeyRight = $relationship->getJunctionKeyB();//$column['relationship']['junction_key_right']; + $JunctionTable = new RelationalTableGateway($junctionTableName, $this->adapter, $this->acl); + $ForeignTable = new RelationalTableGateway($foreignTableName, $this->adapter, $this->acl); + foreach ($foreignDataSet as $junctionRow) { + /** This association is designated for removal */ + $hasPrimaryKey = isset($junctionRow[$JunctionTable->primaryKeyFieldName]); + + if ($hasPrimaryKey && ArrayUtils::get($junctionRow, $this->deleteFlag) === true) { + $Where = new Where; + $Where->equalTo($JunctionTable->primaryKeyFieldName, $junctionRow[$JunctionTable->primaryKeyFieldName]); + $JunctionTable->delete($Where); + // Flag the top-level record as having been altered. + // (disassociating w/ existing M2M collection entry) + $parentCollectionRelationshipsChanged = true; + continue; + } + + /** Update foreign record */ + $foreignRecord = ArrayUtils::get($junctionRow, $junctionKeyRight, []); + if (is_array($foreignRecord)) { + $foreignRecord = $ForeignTable->manageRecordUpdate( + $foreignTableName, + $foreignRecord, + ['activity_mode' => self::ACTIVITY_ENTRY_MODE_CHILD], + $childLogEntries, + $parentCollectionRelationshipsChanged, + $parentData + ); + $foreignJoinColumnKey = $foreignRecord[$ForeignTable->primaryKeyFieldName]; + } else { + $foreignJoinColumnKey = $foreignRecord; + } + + // Junction/Association row + $junctionTableRecord = [ + $junctionKeyLeft => $parentRow[$this->primaryKeyFieldName], + $foreignJoinColumn => $foreignJoinColumnKey + ]; + + // Update fields on the Junction Record + $junctionTableRecord = array_merge($junctionRow, $junctionTableRecord); + + $foreignRecord = (array)$foreignRecord; + + $relationshipChanged = $this->recordDataContainsNonPrimaryKeyData($foreignRecord, $ForeignTable->primaryKeyFieldName) || + $this->recordDataContainsNonPrimaryKeyData($junctionTableRecord, $JunctionTable->primaryKeyFieldName); + + // Update Foreign Record + if ($relationshipChanged) { + $JunctionTable->addOrUpdateRecordByArray($junctionTableRecord, $junctionTableName); + } + } + break; + } + // Once they're managed, remove the foreign collections from the record array + unset($parentRow[$fieldName]); + } + } + + return $parentRow; + } + + public function applyDefaultEntriesSelectParams(array $params) + { + // NOTE: Performance spot + // TODO: Split this, into default and process params + $defaultParams = $this->defaultEntriesSelectParams; + $defaultLimit = $this->getSettings('global', 'default_limit'); + + // Set default rows limit from db settings + if ($defaultLimit) { + $defaultParams['limit'] = (int)$defaultLimit; + } + + // Fetch only one if single param is set + if (ArrayUtils::get($params, 'single')) { + $params['limit'] = 1; + } + + // Remove the columns parameters + // Until we call it fields internally + if (ArrayUtils::has($params, 'columns')) { + ArrayUtils::remove($params, 'columns'); + } + + // NOTE: Let's use "columns" instead of "fields" internally for the moment + if (ArrayUtils::has($params, 'fields')) { + $params['fields'] = ArrayUtils::get($params, 'fields'); + // ArrayUtils::remove($params, 'fields'); + } + + $tableSchema = $this->getTableSchema(); + $sortingField = $tableSchema->getSortingField(); + $defaultParams['sort'] = $sortingField ? $sortingField->getName() : $this->primaryKeyFieldName; + + // Is not there a sort column? + $tableColumns = array_flip(SchemaService::getCollectionFields($this->table, null, true)); + if (!$this->primaryKeyFieldName || !array_key_exists($this->primaryKeyFieldName, $tableColumns)) { + unset($defaultParams['sort']); + } + + $allStatus = ArrayUtils::get($params, 'status') === '*'; + $isAdmin = $this->acl->isAdmin(); + if (!$this->getTableSchema()->hasStatusField() || ($allStatus && $isAdmin)) { + ArrayUtils::remove($params, 'status'); + } else if ($allStatus && !$isAdmin) { + $params['status'] = $this->getNonSoftDeleteStatuses(); + } else if (!ArrayUtils::has($params, 'id') && !ArrayUtils::has($params, 'status')) { + $defaultParams['status'] = $this->getPublishedStatuses(); + } else if (ArrayUtils::has($params, 'status') && is_string(ArrayUtils::get($params, 'status'))) { + $params['status'] = StringUtils::csv($params['status']); + } + + // Remove soft-delete from status list for non-admins users + if (!$isAdmin) { + if (ArrayUtils::has($params, 'status') && !empty($params['status'])) { + $params['status'] = ArrayUtils::intersection( + $params['status'], + $this->getNonSoftDeleteStatuses() + ); + } else if (ArrayUtils::has($params, 'id')) { + $params['status'] = $this->getNonSoftDeleteStatuses(); + } + } + + $params = array_merge($defaultParams, $params); + + if (ArrayUtils::get($params, 'sort')) { + $params['sort'] = StringUtils::csv($params['sort']); + } + + // convert csv columns into array + $columns = convert_param_columns(ArrayUtils::get($params, 'fields', [])); + + // Add columns to params if it's not empty. + // otherwise remove from params + if (!empty($columns)) { + $params['fields'] = $columns; + } else { + ArrayUtils::remove($params, 'fields'); + } + + if ($params['limit'] === null) { + ArrayUtils::remove($params, 'limit'); + } + + array_walk($params, [$this, 'castFloatIfNumeric']); + + return $params; + } + + /** + * @param array $params + * @param Builder $builder + * + * @return Builder + */ + public function applyParamsToTableEntriesSelect(array $params, Builder $builder) + { + // ---------------------------------------------------------------------------- + // STATUS VALUES + // ---------------------------------------------------------------------------- + $statusField = $this->getTableSchema()->getStatusField(); + $permissionStatuses = $this->acl->getCollectionStatusesReadPermission($this->getTable()); + if ($statusField && is_array($permissionStatuses)) { + $paramStatuses = ArrayUtils::get($params, 'status'); + if (is_array($paramStatuses)) { + $permissionStatuses = ArrayUtils::intersection( + $permissionStatuses, + $paramStatuses + ); + } + + $params['status'] = $permissionStatuses; + } + + // @TODO: Query Builder Object + foreach($params as $type => $argument) { + $method = 'process' . ucfirst($type); + if (method_exists($this, $method)) { + call_user_func_array([$this, $method], [$builder, $argument]); + } + } + + $this->applyLegacyParams($builder, $params); + + return $builder; + } + + /** + * Get table items + * + * @param array $params + * + * @return array|mixed + */ + public function getItems(array $params = []) + { + ArrayUtils::remove($params, 'id'); + + return $this->fetchData($params); + } + + /** + * Gets multiple item with the given ids + * + * @param string|int|array $ids + * @param array $params + * + * @return array + */ + public function getItemsByIds($ids, array $params = []) + { + if (is_string($ids) && StringUtils::has($ids, ',')) { + $ids = StringUtils::csv((string)$ids, false); + } + + return $this->fetchData(array_merge($params, [ + 'id' => $ids + ])); + } + + /** + * Gets one item with the given id + * + * @param int|string $id + * @param array $params + * + * @return array + */ + public function getOne($id, array $params = []) + { + return $this->fetchData(array_merge($params, [ + 'single' => true, + 'id' => $id + ])); + } + + public function getOneData($id, array $params = []) + { + return $this->fetchItems(array_merge($params, [ + 'single' => true, + 'id' => $id + ])); + } + + /** + * wrap the query result into the api response format + * + * TODO: This will be soon out of TableGateway + * + * @param array $data + * @param bool $single + * @param bool $meta + * + * @return array + */ + public function wrapData($data, $single = false, $meta = false) + { + $result = []; + + if ($meta) { + if (!is_array($meta)) { + $meta = StringUtils::csv($meta); + } + + $result['meta'] = $this->createMetadata($data, $single, $meta); + } + + $result['data'] = $data; + + return $result; + } + + public function createMetadata($entriesData, $single, $list = []) + { + $singleEntry = $single || !ArrayUtils::isNumericKeys($entriesData); + $metadata = $this->createGlobalMetadata($singleEntry, $list); + + if (!$singleEntry) { + $metadata = array_merge($metadata, $this->createEntriesMetadata($entriesData, $list)); + } + + return $metadata; + } + + /** + * Creates the "global" metadata + * + * @param bool $single + * @param array $list + * + * @return array + */ + public function createGlobalMetadata($single, array $list = []) + { + $allKeys = ['collection', 'type']; + $metadata = []; + + if (empty($list) || in_array('*', $list)) { + $list = $allKeys; + } + + if (in_array('collection', $list)) { + $metadata['collection'] = $this->getTable(); + } + + if (in_array('type', $list)) { + $metadata['type'] = $single ? 'item' : 'collection'; + } + + return $metadata; + } + + /** + * Create entries metadata + * + * @param array $entries + * @param array $list + * + * @return array + */ + public function createEntriesMetadata(array $entries, array $list = []) + { + $allKeys = ['result_count', 'total_count', 'status']; + $tableSchema = $this->getTableSchema($this->table); + + $metadata = []; + + if (empty($list) || in_array('*', $list)) { + $list = $allKeys; + } + + if (in_array('result_count', $list)) { + $metadata['result_count'] = count($entries); + } + + if (in_array('total_count', $list)) { + $metadata['total_count'] = $this->countTotal(); + } + + if ($tableSchema->hasStatusField() && in_array('status', $list)) { + $statusCount = $this->countByStatus(); + $metadata['status'] = $statusCount; + } + + return $metadata; + } + + /** + * Load Table entries + * + * @param array $params + * @param \Closure|null $queryCallback + * + * @return array + * + * @throws Exception\InvalidFieldException + * @throws Exception\ItemNotFoundException + */ + public function fetchItems(array $params = [], \Closure $queryCallback = null) + { + $collectionObject = $this->getTableSchema(); + + $params = $this->applyDefaultEntriesSelectParams($params); + $fields = ArrayUtils::get($params, 'fields'); + + if (is_array($fields)) { + $this->validateFields($fields); + } + + // TODO: Check for all collections + fields permission/existence before querying + // TODO: Create a new TableGateway Query Builder based on Query\Builder + $builder = new Builder($this->getAdapter()); + $builder->from($this->getTable()); + + $selectedFields = array_merge( + [$collectionObject->getPrimaryKeyName()], + $this->getSelectedNonAliasFields($fields ?: ['*']) + ); + + $statusField = $collectionObject->getStatusField(); + if ($statusField && $this->acl->getCollectionStatuses($this->table)) { + $selectedFields = array_merge($selectedFields, [$statusField->getName()]); + } + + $builder->columns($selectedFields); + + $builder = $this->applyParamsToTableEntriesSelect( + $params, + $builder + ); + + $this->enforceReadPermission($builder); + + if ($queryCallback !== null) { + $builder = $queryCallback($builder); + } + + // Run the builder Select with this tablegateway + // to run all the hooks against the result + $results = $this->selectWith($builder->buildSelect())->toArray(); + + if (!$results && ArrayUtils::has($params, 'single')) { + if (ArrayUtils::has($params, 'id')) { + $message = sprintf('Item with id "%s" not found', $params['id']); + } else { + $message = 'Item not found'; + } + + throw new Exception\ItemNotFoundException($message); + } + + // ========================================================================== + // Perform data casting based on the column types in our schema array + // and Convert dates into ISO 8601 Format + // ========================================================================== + $results = $this->parseRecord($results); + + $columnsDepth = ArrayUtils::deepLevel(get_unflat_columns($fields)); + if ($columnsDepth > 0) { + $relatedFields = $this->getSelectedRelatedFields($fields); + + $relationalParams = [ + 'meta' => ArrayUtils::get($params, 'meta'), + 'lang' => ArrayUtils::get($params, 'lang') + ]; + + $results = $this->loadRelationalData( + $results, + get_array_flat_columns($relatedFields), + $relationalParams + ); + } + + // When the params column list doesn't include the primary key + // it should be included because each row gateway expects the primary key + // after all the row gateway are created and initiated it only returns the chosen columns + if ($fields && !array_key_exists('*', get_unflat_columns($fields))) { + $visibleColumns = $this->getSelectedFields($fields); + $results = array_map(function ($entry) use ($visibleColumns) { + foreach ($entry as $key => $value) { + if (!in_array($key, $visibleColumns)) { + $entry = ArrayUtils::omit($entry, $key); + } + } + + return $entry; + }, $results); + } + + if ($statusField && $this->acl->getCollectionStatuses($this->table)) { + foreach ($results as $index => &$item) { + $statusId = ArrayUtils::get($item, $statusField->getName()); + $blacklist = $this->acl->getReadFieldBlacklist($this->table, $statusId); + $item = ArrayUtils::omit($item, $blacklist); + if (empty($item)) { + unset($results[$index]); + } + } + + $results = array_values($results); + } + + if (ArrayUtils::has($params, 'single')) { + $results = reset($results); + } + + return $results ? $results : []; + } + + /** + * Fetches items without being wrap into a data attribute + * + * @param array $params + * + * @return array + */ + protected function fetchData(array $params = []) + { + $meta = ArrayUtils::get($params, 'meta', false); + $id = ArrayUtils::get($params, 'id'); + $single = ArrayUtils::get($params, 'single'); + $idsCount = is_array($id) ? count($id) : 1; + + if (!$single && $id && $idsCount == 1) { + $single = $params['single'] = true; + } + + $items = $this->fetchItems($params); + + return $this->wrapData($items, $single, $meta); + } + + /** + * Loads all relational data by depth level + * + * @param $result + * @param array|null $columns + * @param array $params + * + * @return array + */ + protected function loadRelationalData($result, array $columns = [], array $params = []) + { + $result = $this->loadManyToOneRelationships($result, $columns, $params); + $result = $this->loadOneToManyRelationships($result, $columns, $params); + $result = $this->loadManyToManyRelationships($result, $columns, $params); + + return $result; + } + + /** + * Parse Filter "condition" (this is the filter key value) + * + * @param $condition + * + * @return array + */ + protected function parseCondition($condition) + { + // TODO: Add a simplified option for logical + // adding an "or_" prefix + // filters[column][eq]=Value1&filters[column][or_eq]=Value2 + $logical = null; + if (is_array($condition) && isset($condition['logical'])) { + $logical = $condition['logical']; + unset($condition['logical']); + } + + $operator = is_array($condition) ? key($condition) : '='; + $value = is_array($condition) ? current($condition) : $condition; + $not = false; + + return [ + 'operator' => $operator, + 'value' => $value, + 'not' => $not, + 'logical' => $logical + ]; + } + + protected function parseDotFilters(Builder $mainQuery, array $filters) + { + foreach ($filters as $column => $condition) { + if (!is_string($column) || strpos($column, '.') === false) { + continue; + } + + $columnList = $columns = explode('.', $column); + $columnsTable = [ + $this->getTable() + ]; + + $nextColumn = array_shift($columnList); + $nextTable = $this->getTable(); + $relational = SchemaService::hasRelationship($nextTable, $nextColumn); + + while ($relational) { + $nextTable = SchemaService::getRelatedCollectionName($nextTable, $nextColumn); + $nextColumn = array_shift($columnList); + $relational = SchemaService::hasRelationship($nextTable, $nextColumn); + $columnsTable[] = $nextTable; + } + + // if one of the column in the list has not relationship + // it will break the loop before going over all the columns + // which we will call this as column not found + // TODO: Better error message + if (!empty($columnList)) { + throw new Exception\FieldNotFoundException($nextColumn); + } + + // Remove the original filter column with dot-notation + unset($filters[$column]); + + // Reverse all the columns from comments.author.id to id.author.comments + // To filter from the most deep relationship to their parents + $columns = explode('.', column_identifier_reverse($column)); + $columnsTable = array_reverse($columnsTable, true); + + $mainColumn = array_pop($columns); + $mainTable = array_pop($columnsTable); + + // the main query column + // where the filter is going to be applied + $column = array_shift($columns); + $table = array_shift($columnsTable); + + $query = new Builder($this->getAdapter()); + $mainTableObject = $this->getTableSchema($table); + $query->columns([$mainTableObject->getPrimaryField()->getName()]); + $query->from($table); + + $this->doFilter($query, $column, $condition, $table); + + $index = 0; + foreach ($columns as $key => $column) { + ++$index; + + $oldQuery = $query; + $query = new Builder($this->getAdapter()); + $collection = $this->getTableSchema($columnsTable[$key]); + $field = $collection->getField($column); + + $selectColumn = $collection->getPrimaryField()->getName(); + $table = $columnsTable[$key]; + + if ($field->isAlias()) { + $column = $collection->getPrimaryField()->getName(); + } + + if ($field->isManyToMany()) { + $selectColumn = $field->getRelationship()->getJunctionKeyA(); + $column = $field->getRelationship()->getJunctionKeyB(); + $table = $field->getRelationship()->getJunctionCollection(); + } + + $query->columns([$selectColumn]); + $query->from($table); + $query->whereIn($column, $oldQuery); + } + + $collection = $this->getTableSchema($mainTable); + $field = $collection->getField($mainColumn); + $relationship = $field->getRelationship(); + + // TODO: Make all this whereIn duplication into a function + // TODO: Can we make the O2M simpler getting the parent id from itself + // right now is creating one unnecessary select + if ($field->isManyToMany() || $field->isOneToMany()) { + $mainColumn = $collection->getPrimaryField()->getName(); + $oldQuery = $query; + $query = new Builder($this->getAdapter()); + + if ($field->isManyToMany()) { + $selectColumn = $relationship->getJunctionKeyB(); + $table = $relationship->getJunctionCollection(); + $column = $relationship->getJunctionKeyA(); + } else { + $selectColumn = $column = $relationship->getJunctionKeyA(); + $table = $relationship->getCollectionB(); + } + + $query->columns([$selectColumn]); + $query->from($table); + $query->whereIn( + $column, + $oldQuery + ); + } + + $this->doFilter( + $mainQuery, + $mainColumn, + [ + 'in' => $query + ], + $mainTable + ); + } + + return $filters; + } + + protected function doFilter(Builder $query, $column, $condition, $table) + { + $fieldName = $this->getColumnFromIdentifier($column); + $field = $this->getField( + $fieldName, + // $table will be the default value to get + // if the column has not identifier format + $this->getTableFromIdentifier($column, $table) + ); + + if (!$field) { + throw new Exception\InvalidFieldException($fieldName); + } + + $condition = $this->parseCondition($condition); + $operator = ArrayUtils::get($condition, 'operator'); + $value = ArrayUtils::get($condition, 'value'); + $not = ArrayUtils::get($condition, 'not'); + $logical = ArrayUtils::get($condition, 'logical'); + + // TODO: if there's more, please add a better way to handle all this + if ($field->isToMany()) { + // translate some non-x2m relationship filter to x2m equivalent (if exists) + switch ($operator) { + case 'empty': + // convert x2m empty + // to not has at least one record + $operator = 'has'; + $not = true; + $value = 1; + break; + } + } + + // Get information about the operator shorthand + if (ArrayUtils::has($this->operatorShorthand, $operator)) { + $operatorShorthand = $this->operatorShorthand[$operator]; + $operator = ArrayUtils::get($operatorShorthand, 'operator', $operator); + $not = ArrayUtils::get($operatorShorthand, 'not', !$value); + } + + $operatorName = StringUtils::underscoreToCamelCase(strtolower($operator), true); + $method = 'where' . ($not === true ? 'Not' : '') . $operatorName; + if (!method_exists($query, $method)) { + return false; + } + + $splitOperators = ['between', 'in']; + // TODO: Add exception for API 2.0 + if (in_array($operator, $splitOperators) && is_scalar($value)) { + $value = explode(',', $value); + } + + $arguments = [$column, $value]; + + if (isset($logical)) { + $arguments[] = null; + $arguments[] = $logical; + } + + if (in_array($operator, ['all', 'has']) && $field->isToMany()) { + if ($operator == 'all' && is_string($value)) { + $value = array_map(function ($item) { + return trim($item); + }, explode(',', $value)); + } else if ($operator == 'has') { + $value = (int) $value; + } + + $primaryKey = $this->getTableSchema($table)->getPrimaryField()->getName(); + $relationship = $field->getRelationship(); + if ($relationship->getType() == 'ONETOMANY') { + $arguments = [ + $primaryKey, + $relationship->getCollectionB(), + null, + $relationship->getJunctionKeyB(), + $value + ]; + } else { + $arguments = [ + $primaryKey, + $relationship->getJunctionCollection(), + $relationship->getJunctionKeyA(), + $relationship->getJunctionKeyB(), + $value + ]; + } + } + + // TODO: Move this into QueryBuilder if possible + if (in_array($operator, ['like']) && $field->isManyToOne()) { + $relatedTable = $field->getRelationship()->getCollectionB(); + $tableSchema = SchemaService::getCollection($relatedTable); + $relatedTableColumns = $tableSchema->getFields(); + $relatedPrimaryColumnName = $tableSchema->getPrimaryField()->getName(); + $query->orWhereRelational($this->getColumnFromIdentifier($column), $relatedTable, $relatedPrimaryColumnName, function (Builder $query) use ($column, $relatedTable, $relatedTableColumns, $value) { + $query->nestOrWhere(function (Builder $query) use ($relatedTableColumns, $relatedTable, $value) { + foreach ($relatedTableColumns as $column) { + // NOTE: Only search numeric or string type columns + $isNumeric = $this->getSchemaManager()->isNumericType($column->getType()); + $isString = $this->getSchemaManager()->isStringType($column->getType()); + if (!$column->isAlias() && ($isNumeric || $isString)) { + $query->orWhereLike($column->getName(), $value); + } + } + }); + }); + } else { + call_user_func_array([$query, $method], $arguments); + } + } + + /** + * Process Select Filters (Where conditions) + * + * @param Builder $query + * @param array $filters + */ + protected function processFilter(Builder $query, array $filters = []) + { + $filters = $this->parseDotFilters($query, $filters); + + foreach ($filters as $column => $condition) { + if ($condition instanceof Filter) { + $column = $condition->getIdentifier(); + $condition = $condition->getValue(); + } + + $this->doFilter($query, $column, $condition, $this->getTable()); + } + } + + /** + * Process column joins + * + * @param Builder $query + * @param array $joins + */ + protected function processJoins(Builder $query, array $joins = []) + { + // @TODO allow passing columns + $columns = []; // leave as this and won't get any ambiguous columns + foreach ($joins as $table => $params) { + // TODO: Reduce this into a simpler instructions + // by simpler it means remove the duplicate join() line + if (isset($params['on'])) { + // simple joins style + // 'table' => ['on' => ['col1', 'col2'] ] + if (!isset($params['type'])) { + $params['type'] = 'INNER'; + } + + $params['on'] = implode('=', $params['on']); + + $query->join($table, $params['on'], $columns, $params['type']); + } else { + // many join style + // 'table' => [ ['on' => ['col1', 'col2'] ] ] + foreach ($params as $method => $options) { + if (! isset($options['type'])) { + $options['type'] = 'INNER'; + } + $query->join($table, $options['on'], $columns, $options['type']); + } + } + } + } + + /** + * Process group-by + * + * @param Builder $query + * @param array|string $columns + */ + protected function processGroups(Builder $query, $columns = []) + { + if (!is_array($columns)) { + $columns = explode(',', $columns); + } + + $query->groupBy($columns); + } + + /** + * Process Query search + * + * @param Builder $query + * @param $search + */ + protected function processQ(Builder $query, $search) + { + $columns = SchemaService::getAllCollectionFields($this->getTable()); + $table = $this->getTable(); + + $query->nestWhere(function (Builder $query) use ($columns, $search, $table) { + foreach ($columns as $column) { + // NOTE: Only search numeric or string type columns + $isNumeric = $this->getSchemaManager()->isNumericType($column->getType()); + $isString = $this->getSchemaManager()->isStringType($column->getType()); + if (!$isNumeric && !$isString) { + continue; + } + + if ($column->isManyToOne()) { + $relationship = $column->getRelationship(); + $relatedTable = $relationship->getCollectionB(); + $tableSchema = SchemaService::getCollection($relatedTable); + $relatedTableColumns = $tableSchema->getFields(); + $relatedPrimaryColumnName = $tableSchema->getPrimaryKeyName(); + $query->orWhereRelational($column->getName(), $relatedTable, $relatedPrimaryColumnName, function (Builder $query) use ($column, $relatedTable, $relatedTableColumns, $search) { + $query->nestOrWhere(function (Builder $query) use ($relatedTableColumns, $relatedTable, $search) { + foreach ($relatedTableColumns as $column) { + // NOTE: Only search numeric or string type columns + $isNumeric = $this->getSchemaManager()->isNumericType($column->getType()); + $isString = $this->getSchemaManager()->isStringType($column->getType()); + if (!$column->isAlias() && ($isNumeric || $isString)) { + $query->orWhereLike($column->getName(), $search); + } + } + }); + }); + } else if ($column->isOneToMany()) { + $relationship = $column->getRelationship(); + $relatedTable = $relationship->getCollectionB(); + $relatedRightColumn = $relationship->getJunctionKeyB(); + $relatedTableColumns = SchemaService::getAllCollectionFields($relatedTable); + + $query->from($table); + // TODO: Test here it may be not setting the proper primary key name + $query->orWhereRelational($this->primaryKeyFieldName, $relatedTable, null, $relatedRightColumn, function(Builder $query) use ($column, $relatedTable, $relatedTableColumns, $search) { + foreach ($relatedTableColumns as $column) { + // NOTE: Only search numeric or string type columns + $isNumeric = $this->getSchemaManager()->isNumericType($column->getType()); + $isString = $this->getSchemaManager()->isStringType($column->getType()); + if (!$column->isAlias() && ($isNumeric || $isString)) { + $query->orWhereLike($column->getName(), $search, false); + } + } + }); + } else if ($column->isManyToMany()) { + // @TODO: Implement Many to Many search + } else if (!$column->isAlias()) { + $query->orWhereLike($column->getName(), $search); + } + } + }); + } + + /** + * Process Select Order + * + * @param Builder $query + * @param array $columns + * + * @throws Exception\InvalidFieldException + */ + protected function processSort(Builder $query, array $columns) + { + foreach ($columns as $column) { + $compact = compact_sort_to_array($column); + $orderBy = key($compact); + $orderDirection = current($compact); + + if ($orderBy !== '?' && !SchemaService::hasCollectionField($this->table, $orderBy, $this->acl === null)) { + throw new Exception\InvalidFieldException($column); + } + + $query->orderBy($orderBy, $orderDirection); + } + } + + /** + * Process Select Limit + * + * @param Builder $query + * @param int $limit + */ + protected function processLimit(Builder $query, $limit) + { + $query->limit((int) $limit); + } + + /** + * Process Select offset + * + * @param Builder $query + * @param int $offset + */ + protected function processOffset(Builder $query, $offset) + { + $query->offset((int) $offset); + } + + /** + * Apply legacy params to support old api requests + * + * @param Builder $query + * @param array $params + * + * @throws Exception\FieldNotFoundException + */ + protected function applyLegacyParams(Builder $query, array $params = []) + { + $skipAcl = $this->acl === null; + if (ArrayUtils::get($params, 'status') && SchemaService::hasStatusField($this->getTable(), $skipAcl)) { + $statuses = $params['status']; + if (!is_array($statuses)) { + $statuses = array_map(function($item) { + return trim($item); + }, explode(',', $params['status'])); + } + + // $statuses = array_filter($statuses, function ($value) { + // return is_numeric($value); + // }); + + if ($statuses) { + $query->whereIn(SchemaService::getStatusFieldName( + $this->getTable(), + $this->acl === null + ), $statuses); + } + } + + if (ArrayUtils::has($params, 'id')) { + $entriesIds = $params['id']; + + if (!is_array($entriesIds)) { + $entriesIds = [$entriesIds]; + } + + $query->whereIn($this->primaryKeyFieldName, $entriesIds); + } + + if (!ArrayUtils::has($params, 'q')) { + $search = ArrayUtils::get($params, 'search', ''); + + if ($search) { + $columns = SchemaService::getAllNonAliasCollectionFields($this->getTable()); + $query->nestWhere(function (Builder $query) use ($columns, $search) { + foreach ($columns as $column) { + if ($column->getType() === 'VARCHAR' || $column->getType()) { + $query->whereLike($column->getName(), $search); + } + } + }, 'or'); + } + } + } + + /** + * Throws error if column or relation is missing values + * @param array $column One schema column representation. + * @param array $requiredKeys Values requiring definition. + * @param string $tableName + * @return void + * @throws \Directus\Database\Exception\RelationshipMetadataException If the required values are undefined. + */ + private function enforceColumnHasNonNullValues($column, $requiredKeys, $tableName) + { + $erroneouslyNullKeys = []; + foreach ($requiredKeys as $key) { + if (!isset($column[$key]) || (strlen(trim($column[$key])) === 0)) { + $erroneouslyNullKeys[] = $key; + } + } + if (!empty($erroneouslyNullKeys)) { + $msg = 'Required column/ui metadata columns on table ' . $tableName . ' lack values: '; + $msg .= implode(' ', $requiredKeys); + throw new Exception\RelationshipMetadataException($msg); + } + } + + /** + * Load one to many relational data + * + * @param array $entries + * @param Field[] $columns + * @param array $params + * + * @return bool|array + */ + public function loadOneToManyRelationships($entries, $columns, array $params = []) + { + $columnsTree = get_unflat_columns($columns); + $visibleColumns = $this->getTableSchema()->getFields(array_keys($columnsTree)); + foreach ($visibleColumns as $alias) { + if (!$alias->isAlias() || !$alias->isOneToMany()) { + continue; + } + + $relatedTableName = $alias->getRelationship()->getCollectionB(); + if ($this->acl && !SchemaService::canGroupReadCollection($relatedTableName)) { + continue; + } + + $primaryKey = $this->primaryKeyFieldName; + $callback = function($row) use ($primaryKey) { + return ArrayUtils::get($row, $primaryKey, null); + }; + + $ids = array_unique(array_filter(array_map($callback, $entries))); + if (empty($ids)) { + continue; + } + + // Only select the fields not on the currently authenticated user group's read field blacklist + $relationalColumnName = $alias->getRelationship()->getFieldB(); + $tableGateway = new RelationalTableGateway($relatedTableName, $this->adapter, $this->acl); + $filterFields = get_array_flat_columns($columnsTree[$alias->getName()]); + $filters = []; + if (ArrayUtils::get($params, 'lang')) { + $langIds = StringUtils::csv(ArrayUtils::get($params, 'lang')); + $filters[$alias->getOptions('left_column_name')] = ['in' => $langIds]; + } + + $results = $tableGateway->fetchItems(array_merge([ + 'fields' => array_merge([$relationalColumnName], $filterFields), + // Fetch all related data + 'limit' => -1, + 'filter' => array_merge($filters, [ + $relationalColumnName => ['in' => $ids] + ]), + ], $params)); + + $relatedEntries = []; + $selectedFields = $tableGateway->getSelectedFields($filterFields); + + foreach ($results as $row) { + // Quick fix + // @NOTE: When fetching a column that also has another relational field + // the value is not a scalar value but an array with all the data associated to it. + // @TODO: Make this result a object so it can be easy to interact. + // $row->getId(); RowGateway perhaps? + $relationalColumnId = $row[$relationalColumnName]; + if (is_array($relationalColumnId)) { + $relationalColumnId = $relationalColumnId[$tableGateway->primaryKeyFieldName]; + } + + if (!in_array('*', $filterFields)) { + $row = ArrayUtils::pick( + $row, + $selectedFields + ); + } + + $relatedEntries[$relationalColumnId][] = $row; + } + + // Replace foreign keys with foreign rows + $relationalColumnName = $alias->getName(); + foreach ($entries as &$parentRow) { + // TODO: Remove all columns not from the original selection + // meaning remove the related column and primary key that were selected + // but weren't requested at first but were forced to be selected + // within directus as directus needs the related and the primary keys to work properly + $rows = ArrayUtils::get($relatedEntries, $parentRow[$primaryKey], []); + $rows = $this->applyHook('load.relational.onetomany', $rows, ['column' => $alias]); + $parentRow[$relationalColumnName] = $rows; + } + } + + return $entries; + } + + /** + * Load many to many relational data + * + * @param array $entries + * @param Field[] $columns + * @param array $params + * + * @return bool|array + */ + public function loadManyToManyRelationships($entries, $columns, array $params = []) + { + $columnsTree = get_unflat_columns($columns); + $visibleFields = $this->getTableSchema()->getFields(array_keys($columnsTree)); + + foreach ($visibleFields as $alias) { + if (!$alias->isAlias() || !$alias->isManyToMany()) { + continue; + } + + $relatedTableName = $alias->getRelationship()->getCollectionB(); + if ($this->acl && !SchemaService::canGroupReadCollection($relatedTableName)) { + continue; + } + + $primaryKey = $this->primaryKeyFieldName; + $callback = function($row) use ($primaryKey) { + return ArrayUtils::get($row, $primaryKey, null); + }; + + $ids = array_unique(array_filter(array_map($callback, $entries))); + if (empty($ids)) { + continue; + } + + $junctionKeyLeftColumn = $alias->getRelationship()->getJunctionKeyA(); + $junctionTableName = $alias->getRelationship()->getJunctionCollection(); + $junctionTableGateway = new RelationalTableGateway($junctionTableName, $this->getAdapter(), $this->acl); + $junctionPrimaryKey = SchemaService::getCollectionPrimaryKey($junctionTableName); + + $selectedFields = null; + $fields = $columnsTree[$alias->getName()]; + if ($fields) { + $selectedFields = get_array_flat_columns($fields); + array_unshift($selectedFields, $junctionPrimaryKey); + } + + $results = $junctionTableGateway->fetchItems(array_merge([ + // Fetch all related data + 'limit' => -1, + // Add the aliases of the join columns to prevent being removed from array + // because there aren't part of the "visible" columns list + 'fields' => $selectedFields, + 'filter' => [ + new In( + $junctionKeyLeftColumn, + $ids + ) + ], + ], $params)); + + $relationalColumnName = $alias->getName(); + $relatedEntries = []; + foreach ($results as $row) { + $relatedEntries[$row[$junctionKeyLeftColumn]][] = $row; + } + + // Replace foreign keys with foreign rows + foreach ($entries as &$parentRow) { + $parentRow[$relationalColumnName] = ArrayUtils::get( + $relatedEntries, + $parentRow[$primaryKey], + [] + ); + } + } + + return $entries; + } + + /** + * Fetch related, foreign rows for a whole rowset's ManyToOne relationships. + * (Given a table's schema and rows, iterate and replace all of its foreign + * keys with the contents of these foreign rows.) + * + * @param array $entries Table rows + * @param Field[] $columns + * @param array $params + * + * @return array Revised table rows, now including foreign rows + * + * @throws Exception\RelationshipMetadataException + */ + public function loadManyToOneRelationships($entries, $columns, array $params = []) + { + $columnsTree = get_unflat_columns($columns); + $visibleColumns = $this->getTableSchema()->getFields(array_keys($columnsTree)); + foreach ($visibleColumns as $column) { + if (!$column->isManyToOne()) { + continue; + } + + $relatedTable = $column->getRelationship()->getCollectionB(); + + // if user doesn't have permission to view the related table + // fill the data with only the id, which the user has permission to + if ($this->acl && !SchemaService::canGroupReadCollection($relatedTable)) { + $tableGateway = new RelationalTableGateway($relatedTable, $this->adapter, null); + $primaryKeyName = $tableGateway->primaryKeyFieldName; + + foreach ($entries as $i => $entry) { + $entries[$i][$column->getName()] = [ + $primaryKeyName => $entry[$column->getName()] + ]; + } + + continue; + } + + $tableGateway = new RelationalTableGateway($relatedTable, $this->adapter, $this->acl); + $primaryKeyName = $tableGateway->primaryKeyFieldName; + + if (!$relatedTable) { + $message = 'Non single_file Many-to-One relationship lacks `related_table` value.'; + + if ($column->getName()) { + $message .= ' Column: ' . $column->getName(); + } + + if ($column->getCollectionName()) { + $message .= ' Table: ' . $column->getCollectionName(); + } + + throw new Exception\RelationshipMetadataException($message); + } + + // Aggregate all foreign keys for this relationship (for each row, yield the specified foreign id) + $relationalColumnName = $column->getName(); + $yield = function ($row) use ($relationalColumnName, $entries, $primaryKeyName) { + if (array_key_exists($relationalColumnName, $row)) { + $value = $row[$relationalColumnName]; + if (is_array($value)) { + $value = isset($value[$primaryKeyName]) ? $value[$primaryKeyName] : 0; + } + + return $value; + } + }; + + $ids = array_unique(array_filter(array_map($yield, $entries))); + if (empty($ids)) { + continue; + } + + $filterColumns = get_array_flat_columns($columnsTree[$column->getName()]); + // Fetch the foreign data + $results = $tableGateway->fetchItems(array_merge([ + // Fetch all related data + 'limit' => -1, + // Make sure to include the primary key + 'fields' => array_merge([$primaryKeyName], $filterColumns), + 'filter' => [ + $primaryKeyName => ['in' => $ids] + ], + ], $params)); + + $relatedEntries = []; + foreach ($results as $row) { + $rowId = $row[$primaryKeyName]; + if (!in_array('*', $filterColumns)) { + $row = ArrayUtils::pick( + $row, + $tableGateway->getSelectedFields($filterColumns) + ); + } + + $relatedEntries[$rowId] = $row; + + $tableGateway->wrapData( + $relatedEntries[$rowId], + true, + ArrayUtils::get($params, 'meta', 0) + ); + } + + // Replace foreign keys with foreign rows + foreach ($entries as &$parentRow) { + if (array_key_exists($relationalColumnName, $parentRow)) { + // @NOTE: Not always will be a integer + $foreign_id = (int)$parentRow[$relationalColumnName]; + $parentRow[$relationalColumnName] = null; + // "Did we retrieve the foreign row with this foreign ID in our recent query of the foreign table"? + if (array_key_exists($foreign_id, $relatedEntries)) { + $parentRow[$relationalColumnName] = $relatedEntries[$foreign_id]; + } + } + } + } + + return $entries; + } + + /** + * + * HELPER FUNCTIONS + * + **/ + + /** + * @param $fields + * + * @return array + */ + public function getSelectedFields(array $fields) + { + return $this->replaceWildcardFieldWith( + $fields, + SchemaService::getAllCollectionFieldsName($this->getTable()) + ); + } + + /** + * Gets the non alias fields from the selected fields + * + * @param array $fields + * + * @return array + */ + public function getSelectedNonAliasFields(array $fields) + { + $nonAliasFields = SchemaService::getAllNonAliasCollectionFieldsName($this->getTableSchema()->getName()); + $allFields = $this->replaceWildcardFieldWith( + $fields, + $nonAliasFields + ); + + // Remove alias fields + return ArrayUtils::intersection( + $allFields, + $nonAliasFields + ); + } + + /** + * Returns the related fields from the selected fields array + * + * @param array $fields + * + * @return array + */ + public function getSelectedRelatedFields(array $fields) + { + $fieldsLevel = get_unflat_columns($fields); + + foreach ($fieldsLevel as $parent => $children) { + if ($parent === '*') { + $parentFields = $fieldsLevel[$parent]; + unset($fieldsLevel[$parent]); + $allFields = SchemaService::getAllCollectionFieldsName($this->getTable()); + foreach ($allFields as $field) { + if (isset($fieldsLevel[$field])) { + continue; + } + + $fieldsLevel[$field] = $parentFields; + } + + break; + } + } + + $relatedFields = ArrayUtils::intersection( + array_keys($fieldsLevel), + $this->getTableSchema()->getRelationalFieldsName() + ); + + return array_filter($fieldsLevel, function ($key) use ($relatedFields) { + return in_array($key, $relatedFields); + }, ARRAY_FILTER_USE_KEY); + } + + /** + * Remove the wildcards fields and append the replacement fields + * + * @param array $fields + * @param array $replacementFields + * + * @return array + */ + protected function replaceWildcardFieldWith(array $fields, array $replacementFields) + { + $selectedNames = get_columns_flat_at($fields, 0); + // remove duplicate field name + $selectedNames = array_unique($selectedNames); + + $wildCardIndex = array_search('*', $selectedNames); + if ($wildCardIndex !== false) { + unset($selectedNames[$wildCardIndex]); + $selectedNames = array_merge($selectedNames, $replacementFields); + } + + $pickedNames = array_filter($selectedNames, function ($value) { + return strpos($value, '-') !== 0; + }); + $omittedNames = array_values(array_map(function ($value) { + return substr($value, 1); + }, array_filter($selectedNames, function ($value) { + return strpos($value, '-') === 0; + }))); + + return array_values(array_flip(ArrayUtils::omit(array_flip($pickedNames), $omittedNames))); + } + + /** + * Throws an exception if any of the given field doesn't exists + * + * @param array $fields + * + * @throws Exception\InvalidFieldException + */ + public function validateFields(array $fields) + { + $collection = $this->getTableSchema(); + $selectedFields = $this->getSelectedFields($fields); + + foreach ($selectedFields as $field) { + if (!$collection->hasField($field)) { + throw new Exception\InvalidFieldException($field); + } + } + } + + /** + * Does this record representation contain non-primary-key information? + * Used to determine whether or not to update a foreign record, above and + * beyond simply assigning it to a parent. + * @param array|RowGateway $record + * @param string $pkFieldName + * @return boolean + */ + public function recordDataContainsNonPrimaryKeyData($record, $pkFieldName = 'id') + { + if (is_subclass_of($record, 'Zend\Db\RowGateway\AbstractRowGateway')) { + $record = $record->toArray(); + } elseif (!is_array($record)) { + throw new \InvalidArgumentException('$record must an array or a subclass of AbstractRowGateway'); + } + + $keyCount = count($record); + + return array_key_exists($pkFieldName, $record) ? $keyCount > 1 : $keyCount > 0; + } + + /** + * Update a collection of records within this table. + * @param array $entries Array of records. + * @return void + */ + public function updateCollection($entries) + { + $entries = ArrayUtils::isNumericKeys($entries) ? $entries : [$entries]; + foreach ($entries as $entry) { + $entry = $this->updateRecord($entry); + $entry->save(); + } + } + + /** + * Get the total entries count + * + * @param PredicateInterface|null $predicate + * + * @return int + */ + public function countTotal(PredicateInterface $predicate = null) + { + $select = new Select($this->table); + $select->columns(['total' => new Expression('COUNT(*)')]); + if (!is_null($predicate)) { + $select->where($predicate); + } + + $sql = new Sql($this->adapter, $this->table); + $statement = $sql->prepareStatementForSqlObject($select); + $results = $statement->execute(); + $row = $results->current(); + + return (int) $row['total']; + } + + /** + * Only run on tables which have an status column. + * @return array + */ + public function countActive() + { + return $this->countByStatus(); + } + + public function countByStatus() + { + $collection = $this->schemaManager->getCollection($this->getTable()); + if (!$collection->hasStatusField()) { + return []; + } + + $statusFieldName = $collection->getStatusField()->getName(); + + $select = new Select($this->getTable()); + $select + ->columns([$statusFieldName, 'quantity' => new Expression('COUNT(*)')]) + ->group($statusFieldName); + + $sql = new Sql($this->adapter, $this->table); + $statement = $sql->prepareStatementForSqlObject($select); + $results = $statement->execute(); + + $statusMap = $this->getStatusMapping(); + $stats = []; + foreach ($results as $row) { + if (isset($row[$statusFieldName])) { + foreach ($statusMap as $status) { + if ($status->getValue() == $row[$statusFieldName]) { + $stats[$status->getName()] = (int) $row['quantity']; + } + } + } + } + + $vals = []; + foreach ($statusMap as $value) { + array_push($vals, $value->getName()); + } + + $possibleValues = array_values($vals); + $makeMeZero = array_diff($possibleValues, array_keys($stats)); + foreach ($makeMeZero as $unsetActiveColumn) { + $stats[$unsetActiveColumn] = 0; + } + + $stats['total_entries'] = array_sum($stats); + + return $stats; + } +} diff --git a/src/core/Directus/Database/TableGatewayFactory.php b/src/core/Directus/Database/TableGatewayFactory.php new file mode 100644 index 0000000000..5ddbc7d46f --- /dev/null +++ b/src/core/Directus/Database/TableGatewayFactory.php @@ -0,0 +1,63 @@ + \Directus\Database\TableGateway\DirectusUsersTableGateway + * + * @param $tableName + * @param array $options + * + * @return mixed + */ + public static function create($tableName, $options = []) + { + $tableGatewayClassName = Formatting::underscoreToCamelCase($tableName) . 'TableGateway'; + $namespace = __NAMESPACE__ . '\\TableGateway\\'; + $tableGatewayClassName = $namespace . $tableGatewayClassName; + + $acl = ArrayUtils::get($options, 'acl'); + $dbConnection = ArrayUtils::get($options, 'connection'); + + if (static::$container) { + if ($acl === null) { + $acl = static::$container->get('acl'); + } + + if ($dbConnection === null) { + // TODO: Replace "database" for "connection" + $dbConnection = static::$container->get('database'); + } + } + + if (!$acl) { + $acl = null; + } + + if (class_exists($tableGatewayClassName)) { + $instance = new $tableGatewayClassName($dbConnection, $acl); + } else { + $instance = new RelationalTableGateway($tableName, $dbConnection, $acl); + } + + return $instance; + } +} diff --git a/src/core/Directus/Database/composer.json b/src/core/Directus/Database/composer.json new file mode 100644 index 0000000000..d573b000da --- /dev/null +++ b/src/core/Directus/Database/composer.json @@ -0,0 +1,33 @@ +{ + "name": "directus/database", + "description": "Directus Database Component", + "keywords": [ + "directus", + "database" + ], + "license": "MIT", + "require": { + "php": ">=5.5.0", + "zendframework/zend-db": "dev-directus", + "directus/collection": "^1.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0" + }, + "autoload": { + "psr-4": { + "Directus\\Database\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-version/6.4": "0.9.x-dev" + } + }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/wellingguzman/zend-db" + } + ] +} diff --git a/src/core/Directus/Embed/EmbedManager.php b/src/core/Directus/Embed/EmbedManager.php new file mode 100644 index 0000000000..0cc4a6cbdf --- /dev/null +++ b/src/core/Directus/Embed/EmbedManager.php @@ -0,0 +1,69 @@ +providers as $provider) { + if ($provider->validateURL($url)) { + return $provider->parse($url); + } + } + + throw new \Exception('No Providers registered.'); + } + + /** + * Register a provider + * @param ProviderInterface $provider + * @return ProviderInterface + */ + public function register(ProviderInterface $provider) + { + if (!array_key_exists($provider->getName(), $this->providers)) { + $this->providers[$provider->getName()] = $provider; + } + + return $this->providers[$provider->getName()]; + } + + /** + * Get a registered provider + * @param $name + * @return ProviderInterface|null + */ + public function get($name) + { + return array_key_exists($name, $this->providers) ? $this->providers[$name] : null; + } + + /** + * Get a registered provider by embed type + * @param $type + * @return ProviderInterface|null + */ + public function getByType($type) + { + preg_match('/embed\/([a-zA-Z0-9]+)/', $type, $matches); + + $name = isset($matches[1]) ? $matches[1] : null; + + return $name ? $this->get($name) : null; + } +} diff --git a/src/core/Directus/Embed/Provider/AbstractProvider.php b/src/core/Directus/Embed/Provider/AbstractProvider.php new file mode 100644 index 0000000000..98973d363f --- /dev/null +++ b/src/core/Directus/Embed/Provider/AbstractProvider.php @@ -0,0 +1,137 @@ +config = $config; + } + + /** + * Parse a given URL + * @param $url + * @return mixed + */ + public function parse($url) + { + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException('Invalid or unsupported URL'); + } + + if (!$this->validateURL($url)) { + throw new \InvalidArgumentException( + sprintf('URL: "%s" cannot be parsed by "%s"', $url, get_class($this)) + ); + } + + $embedID = $this->parseURL($url); + + return $this->parseID($embedID); + } + + /** + * Get the embed provider name + * @return string + */ + public function getName() + { + return strtolower($this->name); + } + + /** + * Get the embed type + * @return string + */ + public function getType() + { + return 'embed/' . $this->getName(); + } + + /** + * @inheritDoc + */ + public function getCode($data) + { + return StringUtils::replacePlaceholder($this->getFormatTemplate(), $data); + } + + /** + * @inheritdoc + */ + public function getUrl($data) + { + return StringUtils::replacePlaceholder($this->getFormatUrl(), $data); + } + + /** + * Get the HTML embed format template + * @return mixed + */ + protected function getFormatTemplate() + { + return ''; + } + + /** + * Parse an embed ID + * @param $embedID + * @return array + */ + public function parseID($embedID) + { + $defaultInfo = [ + 'embed' => $embedID, + 'title' => sprintf('%s %s: %s', $this->getName(), $this->getProviderType(), $embedID), + 'filesize' => 0, + 'filename' => $this->getName() . '_' . $embedID . '.jpg', + 'type' => $this->getType() + ]; + + $info = array_merge($defaultInfo, $this->fetchInfo($embedID)); + $info['html'] = $this->getCode($info); + + return $info; + } + + /** + * Get the provider type + * @return mixed + */ + abstract public function getProviderType(); + + /** + * Parsing the url and returning the provider ID + * This is a method use for the extended class + * @param $url + * @return string + * @throws \Exception + */ + abstract protected function parseURL($url); + + /** + * Fetch the embed information + * @param $embedID + * @return array + */ + abstract protected function fetchInfo($embedID); +} diff --git a/src/core/Directus/Embed/Provider/ProviderInterface.php b/src/core/Directus/Embed/Provider/ProviderInterface.php new file mode 100644 index 0000000000..98465472c3 --- /dev/null +++ b/src/core/Directus/Embed/Provider/ProviderInterface.php @@ -0,0 +1,79 @@ +getThumbnail($result['thumbnail_large']); + + return $info; + } + + /** + * Fetch Video thumbnail data + * @param $thumb - url + * @return string + */ + protected function getThumbnail($thumb) + { + $content = @file_get_contents($thumb); + $thumbnail = ''; + + if ($content) { + $thumbnail = 'data:image/jpeg;base64,' . base64_encode($content); + } + + return $thumbnail; + } + + /** + * @inheritDoc + */ + protected function getFormatTemplate() + { + return ''; + } +} diff --git a/src/core/Directus/Embed/Provider/YoutubeProvider.php b/src/core/Directus/Embed/Provider/YoutubeProvider.php new file mode 100644 index 0000000000..19d8525580 --- /dev/null +++ b/src/core/Directus/Embed/Provider/YoutubeProvider.php @@ -0,0 +1,129 @@ +getThumbnail($videoID); + + if (!isset($this->config['youtube_api_key']) || empty($this->config['youtube_api_key'])) { + return $info; + } + + $youtubeFormatUrlString = 'https://www.googleapis.com/youtube/v3/videos?id=%s&key=%s&part=snippet,contentDetails'; + $url = sprintf($youtubeFormatUrlString, $videoID, $this->config['youtube_api_key']); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_URL, $url); + $response = curl_exec($ch); + + $content = json_decode($response); + if (!$content) { + return $info; + } + + if (property_exists($content, 'error')) { + throw new \Exception('Bad YouTube API Key'); + } + + if (property_exists($content, 'items') && count($content->items) > 0) { + $videoDataSnippet = $content->items[0]->snippet; + + $info['title'] = $videoDataSnippet->title; + $info['description'] = $videoDataSnippet->description; + $tags = ''; + if (property_exists($videoDataSnippet, 'tags')) { + $tags = implode(',', $videoDataSnippet->tags); + } + $info['tags'] = $tags; + + $videoContentDetails = $content->items[0]->contentDetails; + $videoStart = new \DateTime('@0'); // Unix epoch + $videoStart->add(new \DateInterval($videoContentDetails->duration)); + $info['duration'] = $videoStart->format('U'); + } + + return $info; + } + + /** + * Fetch Video thumbnail data + * @param $videoID + * @return string + */ + protected function getThumbnail($videoID) + { + $content = @file_get_contents('http://img.youtube.com/vi/' . $videoID . '/0.jpg'); + + $thumbnail = ''; + if ($content) { + $thumbnail = 'data:image/jpeg;base64,' . base64_encode($content); + } + + return $thumbnail; + } + + /** + * @inheritDoc + */ + protected function getFormatTemplate() + { + return ''; + } +} diff --git a/src/core/Directus/Exception/BadRequestException.php b/src/core/Directus/Exception/BadRequestException.php new file mode 100644 index 0000000000..09a77d4b4c --- /dev/null +++ b/src/core/Directus/Exception/BadRequestException.php @@ -0,0 +1,13 @@ +attributes; + } +} diff --git a/src/core/Directus/Exception/ForbiddenException.php b/src/core/Directus/Exception/ForbiddenException.php new file mode 100644 index 0000000000..9e93f09853 --- /dev/null +++ b/src/core/Directus/Exception/ForbiddenException.php @@ -0,0 +1,8 @@ +uploadedError = $errorCode; + parent::__construct(get_uploaded_file_error($errorCode)); + } + + public function getErrorCode() + { + return static::ERROR_CODE + $this->uploadedError; + } +} diff --git a/src/core/Directus/Filesystem/Exception/FilesystemException.php b/src/core/Directus/Filesystem/Exception/FilesystemException.php new file mode 100644 index 0000000000..8bd1dcf5da --- /dev/null +++ b/src/core/Directus/Filesystem/Exception/FilesystemException.php @@ -0,0 +1,15 @@ + '', + 'tags' => '', + 'location' => '' + ]; + + /** + * Hook Emitter Instance + * + * @var \Directus\Hook\Emitter + */ + protected $emitter; + + public function __construct($filesystem, $config, array $settings, $emitter) + { + $this->filesystem = $filesystem; + $this->config = $config; + $this->emitter = $emitter; + $this->filesSettings = $settings; + } + + // @TODO: remove exists() and rename() method + // and move it to Directus\Filesystem Wrapper + public function exists($path) + { + return $this->filesystem->getAdapter()->has($path); + } + + public function rename($path, $newPath, $replace = false) + { + if ($replace === true && $this->filesystem->exists($newPath)) { + $this->filesystem->getAdapter()->delete($newPath); + } + + return $this->filesystem->getAdapter()->rename($path, $newPath); + } + + public function delete($file) + { + if ($this->exists($file['filename'])) { + $this->emitter->run('files.deleting', [$file]); + $this->filesystem->getAdapter()->delete($file['filename']); + $this->emitter->run('files.deleting:after', [$file]); + } + } + + /** + * Copy $_FILES data into directus media + * + * @param array $file $_FILES data + * + * @return array directus file info data + */ + public function upload(array $file) + { + $filePath = $file['tmp_name']; + $fileName = $file['name']; + + $fileData = array_merge($this->defaults, $this->processUpload($filePath, $fileName)); + + return [ + 'type' => $fileData['type'], + 'name' => $fileData['name'], + 'title' => $fileData['title'], + 'tags' => $fileData['tags'], + 'description' => $fileData['caption'], + 'location' => $fileData['location'], + 'charset' => $fileData['charset'], + 'size' => $fileData['size'], + 'width' => $fileData['width'], + 'height' => $fileData['height'], + // @TODO: Returns date in ISO 8601 Ex: 2016-06-06T17:18:20Z + // see: https://en.wikipedia.org/wiki/ISO_8601 + 'date_uploaded' => $fileData['date_uploaded'],// . ' UTC', + 'storage_adapter' => $fileData['storage_adapter'] + ]; + } + + /** + * Get URL info + * + * @param string $url + * + * @return array + */ + public function getLink($url) + { + // @TODO: use oEmbed + // @TODO: better provider url validation + // checking for 'youtube.com' for a valid youtube video is wrong + // we can also be using youtube.com/img/a/youtube/image.jpg + // which should fallback to ImageProvider + // instead checking for a url with 'youtube.com/watch' with v param or youtu.be/ + $app = Application::getInstance(); + $embedManager = $app->getContainer()->get('embed_manager'); + try { + $info = $embedManager->parse($url); + } catch (\Exception $e) { + $info = $this->getImageFromURL($url); + } + + if ($info) { + $info['upload_date'] = DateTimeUtils::nowInUTC()->toString(); + $info['storage_adapter'] = $this->getConfig('adapter'); + $info['charset'] = isset($info['charset']) ? $info['charset'] : ''; + } + + return $info; + } + + /** + * Gets the mime-type from the content type + * + * @param $contentType + * + * @return string + */ + protected function getMimeTypeFromContentType($contentType) + { + // split the data type if it has charset or boundaries set + // ex: image/jpg;charset=UTF8 + if (strpos($contentType, ';') !== false) { + $contentType = array_map('trim', explode(';', $contentType)); + } + + if (is_array($contentType)) { + $contentType = $contentType[0]; + } + + return $contentType; + } + + /** + * Get Image from URL + * + * @param $url + * @return array + */ + protected function getImageFromURL($url) + { + stream_context_set_default([ + 'http' => [ + 'method' => 'HEAD' + ] + ]); + + $urlHeaders = get_headers($url, 1); + + stream_context_set_default([ + 'http' => [ + 'method' => 'GET' + ] + ]); + + $info = []; + + $contentType = $this->getMimeTypeFromContentType($urlHeaders['Content-Type']); + + if (strpos($contentType, 'image/') === false) { + return $info; + } + + $urlInfo = parse_url_file($url); + $content = file_get_contents($url); + if (!$content) { + return $info; + } + + list($width, $height) = getimagesizefromstring($content); + + $data = 'data:' . $contentType . ';base64,' . base64_encode($content); + $info['title'] = $urlInfo['filename']; + $info['name'] = $urlInfo['basename']; + $info['size'] = isset($urlHeaders['Content-Length']) ? $urlHeaders['Content-Length'] : 0; + $info['type'] = $contentType; + $info['width'] = $width; + $info['height'] = $height; + $info['data'] = $data; + $info['charset'] = 'binary'; + + return $info; + } + + /** + * Get base64 data information + * + * @param $data + * + * @return array + */ + public function getDataInfo($data) + { + if (strpos($data, 'data:') === 0) { + $parts = explode(',', $data); + $data = $parts[1]; + } + + $info = $this->getFileInfoFromData(base64_decode($data)); + + return array_merge(['data' => $data], $info); + } + + /** + * Copy base64 data into Directus Media + * + * @param string $fileData - base64 data + * @param string $fileName - name of the file + * @param bool $replace + * + * @return array + */ + public function saveData($fileData, $fileName, $replace = false) + { + $fileData = base64_decode($this->getDataInfo($fileData)['data']); + + // @TODO: merge with upload() + $fileName = $this->getFileName($fileName, $replace !== true); + + $filePath = $this->getConfig('root') . '/' . $fileName; + + $this->emitter->run('files.saving', ['name' => $fileName, 'size' => strlen($fileData)]); + $this->write($fileName, $fileData, $replace); + $this->emitter->run('files.saving:after', ['name' => $fileName, 'size' => strlen($fileData)]); + + unset($fileData); + + $fileData = $this->getFileInfo($fileName); + $fileData['title'] = Formatting::fileNameToFileTitle($fileName); + $fileData['filename'] = basename($filePath); + $fileData['upload_date'] = DateTimeUtils::nowInUTC()->toString(); + $fileData['storage_adapter'] = $this->config['adapter']; + + $fileData = array_merge($this->defaults, $fileData); + + return [ + 'type' => $fileData['type'], + 'filename' => $fileData['filename'], + 'title' => $fileData['title'], + 'tags' => $fileData['tags'], + 'description' => $fileData['description'], + 'location' => $fileData['location'], + 'charset' => $fileData['charset'], + 'filesize' => $fileData['size'], + 'width' => $fileData['width'], + 'height' => $fileData['height'], + 'storage_adapter' => $fileData['storage_adapter'] + ]; + } + + /** + * Save embed url into Directus Media + * + * @param array $fileInfo - File Data/Info + * + * @return array - file info + */ + public function saveEmbedData(array $fileInfo) + { + if (!array_key_exists('type', $fileInfo) || strpos($fileInfo['type'], 'embed/') !== 0) { + return []; + } + + $fileName = isset($fileInfo['filename']) ? $fileInfo['filename'] : md5(time()) . '.jpg'; + $imageData = $this->saveData($fileInfo['data'], $fileName); + + return array_merge($imageData, $fileInfo, [ + 'filename' => $fileName + ]); + } + + /** + * Get file info + * + * @param string $path - file path + * @param bool $outside - if the $path is outside of the adapter root path. + * + * @throws \RuntimeException + * + * @return array file information + */ + public function getFileInfo($path, $outside = false) + { + if ($outside === true) { + $buffer = file_get_contents($path); + } else { + $buffer = $this->filesystem->getAdapter()->read($path); + } + + return $this->getFileInfoFromData($buffer); + } + + public function getFileInfoFromData($data) + { + if (!class_exists('\finfo')) { + throw new \RuntimeException('PHP File Information extension was not loaded.'); + } + + $finfo = new \finfo(FILEINFO_MIME); + $type = explode('; charset=', $finfo->buffer($data)); + + $mime = $type[0]; + $charset = $type[1]; + $typeTokens = explode('/', $mime); + + $info = [ + 'type' => $mime, + 'format' => $typeTokens[1], + 'charset' => $charset, + 'size' => strlen($data), + 'width' => null, + 'height' => null + ]; + + if ($typeTokens[0] == 'image') { + $meta = []; + // @TODO: use this as fallback for finfo? + $imageInfo = getimagesizefromstring($data, $meta); + + $info['width'] = $imageInfo[0]; + $info['height'] = $imageInfo[1]; + + if (isset($meta['APP13'])) { + $iptc = iptcparse($meta['APP13']); + + if (isset($iptc['2#120'])) { + $info['caption'] = $iptc['2#120'][0]; + } + + if (isset($iptc['2#005']) && $iptc['2#005'][0] != '') { + $info['title'] = $iptc['2#005'][0]; + } + + if (isset($iptc['2#025'])) { + $info['tags'] = implode(',', $iptc['2#025']); + } + + $location = []; + if (isset($iptc['2#090']) && $iptc['2#090'][0] != '') { + $location[] = $iptc['2#090'][0]; + } + + if (isset($iptc['2#095'][0]) && $iptc['2#095'][0] != '') { + $location[] = $iptc['2#095'][0]; + } + + if (isset($iptc['2#101']) && $iptc['2#101'][0] != '') { + $location[] = $iptc['2#101'][0]; + } + + $info['location'] = implode(', ', $location); + } + } + + unset($data); + + return $info; + } + + /** + * Get file settings + * + * @param string $key - Optional setting key name + * + * @return mixed + */ + public function getSettings($key = '') + { + if (!$key) { + return $this->filesSettings; + } else if (array_key_exists($key, $this->filesSettings)) { + return $this->filesSettings[$key]; + } + + return false; + } + + /** + * Get filesystem config + * + * @param string $key - Optional config key name + * + * @return mixed + */ + public function getConfig($key = '') + { + if (!$key) { + return $this->config; + } else if (array_key_exists($key, $this->config)) { + return $this->config[$key]; + } + + return false; + } + + /** + * Writes the given data in the given location + * + * @param $location + * @param $data + * @param bool $replace + * + * @throws \RuntimeException + */ + public function write($location, $data, $replace = false) + { + $this->filesystem->write($location, $data, $replace); + } + + /** + * Reads and returns data from the given location + * + * @param $location + * + * @return bool|false|string + * + * @throws \Exception + */ + public function read($location) + { + try { + return $this->filesystem->getAdapter()->read($location); + } catch (\Exception $e) { + throw $e; + } + } + + /** + * Creates a new file for Directus Media + * + * @param string $filePath + * @param string $targetName + * + * @return array file info + */ + private function processUpload($filePath, $targetName) + { + // set true as $filePath it's outside adapter path + // $filePath is on a temporary php directory + $fileData = $this->getFileInfo($filePath, true); + $mediaPath = $this->filesystem->getPath(); + + $fileData['title'] = Formatting::fileNameToFileTitle($targetName); + + $targetName = $this->getFileName($targetName); + $finalPath = rtrim($mediaPath, '/') . '/' . $targetName; + $data = file_get_contents($filePath); + + $this->emitter->run('files.saving', ['name' => $targetName, 'size' => strlen($data)]); + $this->write($targetName, $data); + $this->emitter->run('files.saving:after', ['name' => $targetName, 'size' => strlen($data)]); + + $fileData['name'] = basename($finalPath); + $fileData['date_uploaded'] = DateTimeUtils::nowInUTC()->toString(); + $fileData['storage_adapter'] = $this->config['adapter']; + + return $fileData; + } + + /** + * Sanitize title name from file name + * + * @param string $fileName + * + * @return string + */ + private function sanitizeName($fileName) + { + // do not start with dot + $fileName = preg_replace('/^\./', 'dot-', $fileName); + $fileName = str_replace(' ', '_', $fileName); + + return $fileName; + } + + /** + * Add suffix number to file name if already exists. + * + * @param string $fileName + * @param string $targetPath + * @param int $attempt - Optional + * + * @return bool + */ + private function uniqueName($fileName, $targetPath, $attempt = 0) + { + $info = pathinfo($fileName); + // @TODO: this will fail when the filename doesn't have extension + $ext = $info['extension']; + $name = basename($fileName, ".$ext"); + + $name = $this->sanitizeName($name); + + $fileName = "$name.$ext"; + if ($this->filesystem->exists($fileName)) { + $matches = []; + $trailingDigit = '/\-(\d)\.(' . $ext . ')$/'; + if (preg_match($trailingDigit, $fileName, $matches)) { + // Convert "fname-1.jpg" to "fname-2.jpg" + $attempt = 1 + (int)$matches[1]; + $newName = preg_replace($trailingDigit, "-{$attempt}.$ext", $fileName); + $fileName = basename($newName); + } else { + if ($attempt) { + $name = rtrim($name, $attempt); + $name = rtrim($name, '-'); + } + $attempt++; + $fileName = $name . '-' . $attempt . '.' . $ext; + } + return $this->uniqueName($fileName, $targetPath, $attempt); + } + + return $fileName; + } + + /** + * Get file name based on file naming setting + * + * @param string $fileName + * @param bool $unique + * + * @return string + */ + private function getFileName($fileName, $unique = true) + { + switch ($this->getSettings('file_naming')) { + case 'file_hash': + $fileName = $this->hashFileName($fileName); + break; + } + + if ($unique) { + $fileName = $this->uniqueName($fileName, $this->filesystem->getPath()); + } + + return $fileName; + } + + /** + * Hash file name + * + * @param string $fileName + * + * @return string + */ + private function hashFileName($fileName) + { + $ext = pathinfo($fileName, PATHINFO_EXTENSION); + $fileHashName = md5(microtime() . $fileName); + return $fileHashName . '.' . $ext; + } + + /** + * Get string between two string + * + * @param string $string + * @param string $start + * @param string $end + * + * @return string + */ + private function get_string_between($string, $start, $end) + { + $string = ' ' . $string; + $ini = strpos($string, $start); + if ($ini == 0) return ''; + $ini += strlen($start); + $len = strpos($string, $end, $ini) - $ini; + return substr($string, $ini, $len); + } + + /** + * Get URL info + * + * @param string $link + * + * @return array + */ + public function getLinkInfo($link) + { + $fileData = []; + $width = 0; + $height = 0; + + $urlHeaders = get_headers($link, 1); + $contentType = $this->getMimeTypeFromContentType($urlHeaders['Content-Type']); + + if (strpos($contentType, 'image/') === 0) { + list($width, $height) = getimagesize($link); + } + + $urlInfo = pathinfo($link); + $linkContent = file_get_contents($link); + $url = 'data:' . $contentType . ';base64,' . base64_encode($linkContent); + + $fileData = array_merge($fileData, [ + 'type' => $contentType, + 'name' => $urlInfo['basename'], + 'title' => $urlInfo['filename'], + 'charset' => 'binary', + 'size' => isset($urlHeaders['Content-Length']) ? $urlHeaders['Content-Length'] : 0, + 'width' => $width, + 'height' => $height, + 'data' => $url, + 'url' => ($width) ? $url : '' + ]); + + return $fileData; + } +} diff --git a/src/core/Directus/Filesystem/Filesystem.php b/src/core/Directus/Filesystem/Filesystem.php new file mode 100644 index 0000000000..78a1ae1485 --- /dev/null +++ b/src/core/Directus/Filesystem/Filesystem.php @@ -0,0 +1,98 @@ +adapter = $adapter; + } + + /** + * Check whether a file exists. + * + * @param string $path + * + * @return bool + */ + public function exists($path) + { + return $this->adapter->has($path); + } + + /** + * Reads and returns data from the given location + * + * @param $location + * + * @return bool|false|string + * + * @throws \Exception + */ + public function read($location) + { + return $this->adapter->read($location); + } + + /** + * Writes data to th given location + * + * @param string $location + * @param $data + * @param bool $replace + */ + public function write($location, $data, $replace = false) + { + $throwException = function () use ($location) { + throw new ForbiddenException(sprintf('No permission to write: %s', $location)); + }; + + if ($replace === true && $this->exists($location)) { + $this->getAdapter()->delete($location); + } + + try { + if (!$this->getAdapter()->write($location, $data)) { + $throwException(); + } + } catch (\Exception $e) { + $throwException(); + } + } + + /** + * Get the filesystem adapter (flysystem object) + * + * @return FlysystemInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Get Filesystem adapter path + * + * @param string $path + * @return string + */ + public function getPath($path = '') + { + $adapter = $this->adapter->getAdapter(); + + if ($path) { + return $adapter->applyPathPrefix($path); + } + + return $adapter->getPathPrefix(); + } +} diff --git a/src/core/Directus/Filesystem/FilesystemFactory.php b/src/core/Directus/Filesystem/FilesystemFactory.php new file mode 100644 index 0000000000..e3e9db4024 --- /dev/null +++ b/src/core/Directus/Filesystem/FilesystemFactory.php @@ -0,0 +1,55 @@ +getContainer()->get('path_base') . '/' . $root; + } + + $root = $root ?: '/'; + + return new Flysystem(new LocalAdapter($root)); + } + + public static function createS3Adapter(Array $config, $rootKey = 'root') + { + $client = S3Client::factory([ + 'credentials' => [ + 'key' => $config['key'], + 'secret' => $config['secret'], + ], + 'region' => $config['region'], + 'version' => ($config['version'] ?: 'latest'), + ]); + + return new Flysystem(new S3Adapter($client, $config['bucket'], array_get($config, $rootKey))); + } +} diff --git a/src/core/Directus/Filesystem/Thumbnail.php b/src/core/Directus/Filesystem/Thumbnail.php new file mode 100644 index 0000000000..d024b5715e --- /dev/null +++ b/src/core/Directus/Filesystem/Thumbnail.php @@ -0,0 +1,204 @@ + 1) { + $newW = $thumbnailSize; + $newH = $thumbnailSize / $aspectRatio; + } + } + + if ($cropEnabled) { + $imgResized = imagecreatetruecolor($thumbnailSize, $thumbnailSize); + } else { + $imgResized = imagecreatetruecolor($newW, $newH); + } + + // Preserve transperancy for gifs and pngs + if ($format == 'gif' || $format == 'png') { + imagealphablending($imgResized, false); + imagesavealpha($imgResized, true); + $transparent = imagecolorallocatealpha($imgResized, 255, 255, 255, 127); + imagefilledrectangle($imgResized, 0, 0, $newW, $newH, $transparent); + } + + imagecopyresampled($imgResized, $img, $x1, $y1, 0, 0, $newW, $newH, $w, $h); + + imagedestroy($img); + return $imgResized; + } + + /** + * Create a image from a non image file content. (Ex. PDF, PSD or TIFF) + * + * @param $content + * @param string $format + * + * @return bool|string + */ + public static function createImageFromNonImage($content, $format = 'jpeg') + { + if (!extension_loaded('imagick')) { + return false; + } + + $image = new \Imagick(); + $image->readImageBlob($content); + $image->setIteratorIndex(0); + $image->setImageFormat($format); + + return $image->getImageBlob(); + } + + public static function writeImage($extension, $path, $img, $quality) + { + ob_start(); + // force $path to be NULL to dump writeImage on the stream + $path = NULL; + switch (strtolower($extension)) { + case 'jpg': + case 'jpeg': + imagejpeg($img, $path, $quality); + break; + case 'gif': + imagegif($img, $path); + break; + case 'png': + imagepng($img, $path); + break; + case 'pdf': + case 'psd': + case 'tif': + case 'tiff': + imagejpeg($img, $path, $quality); + break; + } + return ob_get_clean(); + } + + /** + * Gets the default thumbnail format + * + * @return string + */ + public static function defaultFormat() + { + return static::$defaultFormat; + } + + /** + * Gets supported formats + * + * @return array + */ + public static function getFormatsSupported() + { + return array_merge(static::getImageFormatSupported(), static::getNonImageFormatSupported()); + } + + /** + * Gets image supported formats + * + * @return array + */ + public static function getImageFormatSupported() + { + return Thumbnail::$imageFormatsSupported; + } + + /** + * Gets non-image supported formats + * + * @return array + */ + public static function getNonImageFormatSupported() + { + return static::$nonImageFormatsSupported; + } + + /** + * If a given format/extension is a non-image supported to generate thumbnail + * + * @param $format + * + * @return bool + */ + public static function isNonImageFormatSupported($format) + { + return in_array(strtolower($format), static::$nonImageFormatsSupported); + } +} diff --git a/src/core/Directus/Filesystem/Thumbnailer.php b/src/core/Directus/Filesystem/Thumbnailer.php new file mode 100644 index 0000000000..6ffbe63079 --- /dev/null +++ b/src/core/Directus/Filesystem/Thumbnailer.php @@ -0,0 +1,503 @@ +files = $files; + $this->filesystem = $main; + $this->filesystemThumb = $thumb; + $this->config = $config; + + $this->thumbnailParams = $this->extractThumbnailParams($path); + + // check if the original file exists in storage + if (! $this->filesystem->exists($this->fileName)) { + throw new Exception($this->fileName . ' does not exist.'); // original file doesn't exist + } + + // check if dimensions are supported + if (! $this->isSupportedThumbnailDimension($this->width, $this->height)) { + throw new Exception('Invalid dimensions.'); + } + + // check if action is supported + if ( $this->action && ! $this->isSupportedAction($this->action)) { + throw new Exception('Invalid action.'); + } + + // check if quality is supported + if ( $this->quality && ! $this->isSupportedQualityTag($this->quality)) { + throw new Exception('Invalid quality.'); + } + + // relative to configuration['filesystem']['root_thumb'] + $this->thumbnailDir = $pathPrefix . '/' . $this->width . '/' . $this->height . ($this->action ? '/' . $this->action : '') . ($this->quality ? '/' . $this->quality : ''); + } catch (Exception $e) { + throw $e; + } + } + + /** + * Magic getter for thumbnailParams + * + * @param string $key + * @return string|int + */ + public function __get($key) + { + return ArrayUtils::get($this->thumbnailParams, $key, null); + } + + /** + * Return thumbnail as data + * + * @throws Exception + * @return string|null + */ + public function get() + { + try { + if( $this->filesystemThumb->exists($this->thumbnailDir . '/' . $this->fileName) ) { + $img = $this->filesystemThumb->read($this->thumbnailDir . '/' . $this->fileName); + } + + return isset($img) && $img ? $img : null; + } + + catch (Exception $e) { + throw $e; + } + } + + /** + * Get thumbnail mime type + * + * @throws Exception + * @return string + */ + public function getThumbnailMimeType() + { + try { + if( $this->filesystemThumb->exists($this->thumbnailDir . '/' . $this->fileName) ) { + $img = Image::make($this->filesystemThumb->read($this->thumbnailDir . '/' . $this->fileName)); + return $img->mime(); + } + + return 'application/octet-stream'; + } + + catch (Exception $e) { + throw $e; + } + } + + /** + * Create thumbnail from image and `contain` + * http://image.intervention.io/api/resize + * https://css-tricks.com/almanac/properties/o/object-fit/ + * + * @throws Exception + * @return string + */ + public function contain() + { + try { + // action options + $options = $this->getSupportedActionOptions($this->action); + + // open file image resource + $img = Image::make($this->filesystem->read($this->fileName)); + + // crop image + $img->resize($this->width, $this->height, function ($constraint) { + $constraint->aspectRatio(); + }); + + if( ArrayUtils::get($options, 'resizeCanvas')) { + $img->resizeCanvas($this->width, $this->height, ArrayUtils::get($options, 'position', 'center'), ArrayUtils::get($options, 'resizeRelative', false), ArrayUtils::get($options, 'canvasBackground', [255, 255, 255, 0])); + } + + $encodedImg = (string) $img->encode(ArrayUtils::get($this->thumbnailParams, 'fileExt'), ($this->quality ? $this->translateQuality($this->quality) : null)); + $this->filesystemThumb->write($this->thumbnailDir . '/' . $this->fileName, $encodedImg); + + return $encodedImg; + } + + catch (Exception $e) { + throw $e; + } + } + + /** + * Create thumbnail from image and `crop` + * http://image.intervention.io/api/fit + * https://css-tricks.com/almanac/properties/o/object-fit/ + * + * @throws Exception + * @return string + */ + public function crop() + { + try { + // action options + $options = $this->getSupportedActionOptions($this->action); + + // open file image resource + $img = Image::make($this->filesystem->read($this->fileName)); + + // resize/crop image + $img->fit($this->width, $this->height, function($constraint){}, ArrayUtils::get($options, 'position', 'center')); + + $encodedImg = (string) $img->encode(ArrayUtils::get($this->thumbnailParams, 'fileExt'), ($this->quality ? $this->translateQuality($this->quality) : null)); + $this->filesystemThumb->write($this->thumbnailDir . '/' . $this->fileName, $encodedImg); + + return $encodedImg; + } + + catch (Exception $e) { + throw $e; + } + } + + /** + * Parse url and extract thumbnail params + * + * @param string $thumbnailUrlPath + * @throws Exception + * @return array + */ + public function extractThumbnailParams($thumbnailUrlPath) + { + try { + if ($this->thumbnailParams) { + return $this->thumbnailParams; + } + + $urlSegments = explode('/', $thumbnailUrlPath); + + // pull the env out of the segments + array_shift($urlSegments); + + if (! $urlSegments) { + throw new Exception('Invalid thumbnailUrlPath.'); + } + + // pop off the filename + $fileName = ArrayUtils::pop($urlSegments); + + // make sure filename is valid + $info = pathinfo($fileName); + if (! $this->isSupportedFileExtension((ArrayUtils::get($info, 'extension')))) { + throw new Exception('Invalid file extension.'); + } + + $thumbnailParams = [ + 'fileName' => ArrayUtils::get($info, 'filename') . '.' . strtolower(ArrayUtils::get($info, 'extension')), + 'fileExt' => ArrayUtils::get($info, 'extension') + ]; + + foreach ($urlSegments as $segment) { + + if (! $segment) continue; + + $hasWidth = ArrayUtils::get($thumbnailParams, 'width'); + $hasHeight = ArrayUtils::get($thumbnailParams, 'height'); + // extract width and height + if ((!$hasWidth || !$hasHeight) && is_numeric($segment)) { + if (!$hasWidth) { + ArrayUtils::set($thumbnailParams, 'width', $segment); + } else if (!$hasHeight) { + ArrayUtils::set($thumbnailParams, 'height', $segment); + } + } + + // extract action and quality + else { + if (!ArrayUtils::get($thumbnailParams, 'action')) { + ArrayUtils::set($thumbnailParams, 'action', $segment); + } else if (!ArrayUtils::get($thumbnailParams, 'quality')) { + ArrayUtils::set($thumbnailParams, 'quality', $segment); + } + } + } + + // validate + if (! ArrayUtils::contains($thumbnailParams, [ + 'width', + 'height' + ])) { + throw new Exception('No height or width provided.'); + } + + // set default action, if needed + if (! ArrayUtils::exists($thumbnailParams, 'action')) { + ArrayUtils::set($thumbnailParams, 'action', null); + } + + // set quality to null, if needed + if (! ArrayUtils::exists($thumbnailParams, 'quality')) { + ArrayUtils::set($thumbnailParams, 'quality', null); + } + + return $thumbnailParams; + } + + catch (Exception $e) { + throw $e; + } + } + + /** + * Translate quality text to number and return + * + * @param string $qualityText + * @return number + */ + public function translateQuality($qualityText) + { + $quality = 60; + if (!is_numeric($qualityText)) { + $quality = ArrayUtils::get($this->getSupportedQualityTags(), $qualityText, $quality); + } + + return $quality; + } + + /** + * Check if given file extension is supported + * + * @param int $ext + * @return boolean + */ + public function isSupportedFileExtension($ext) + { + return in_array(strtolower($ext), $this->getSupportedFileExtensions()); + } + + /** + * Return supported image file types + * + * @return array + */ + public function getSupportedFileExtensions() + { + return Thumbnail::getFormatsSupported(); + } + + /** + * Check if given dimension is supported + * + * @param int $width + * @param int $height + * @return boolean + */ + public function isSupportedThumbnailDimension($width, $height) + { + return in_array($width . 'x' . $height, $this->getSupportedThumbnailDimensions()); + } + + /** + * Return supported thumbnail file dimesions + * + * @return array + */ + public function getSupportedThumbnailDimensions() + { + $defaultDimension = '200x200'; + + $dimensions = $this->parseCSV( + ArrayUtils::get($this->getConfig(), 'dimensions') + ); + + if (!in_array($defaultDimension, $dimensions)) { + array_unshift($dimensions, $defaultDimension); + } + + return $dimensions; + } + + /** + * Check if given action is supported + * + * @param int $action + * @return boolean + */ + public function isSupportedAction($action) + { + return ArrayUtils::has($this->getSupportedActions(), $action); + } + + /** + * Return supported actions + * + * @return array + */ + public function getSupportedActions() + { + return $this->getActions(); + } + + /** + * Check if given quality is supported + * + * @param $qualityTag + * + * @return bool + */ + public function isSupportedQualityTag($qualityTag) + { + return ArrayUtils::has($this->getSupportedQualityTags(), $qualityTag); + } + + /** + * Return supported thumbnail qualities + * + * @return array + */ + public function getSupportedQualityTags() + { + $defaultQualityTags = [ + 'poor' => 25, + 'good' => 50, + 'better' => 75, + 'best' => 100, + ]; + + $qualityTags = ArrayUtils::get($this->getConfig(), 'quality_tags') ?: []; + if (is_string($qualityTags)) { + $qualityTags = json_decode($qualityTags, true); + } + + return array_merge($defaultQualityTags, $qualityTags); + } + + /** + * Return supported action options as set in config + * + * @param string $action + * + * @return array + */ + public function getSupportedActionOptions($action) + { + return ArrayUtils::get($this->getActions(), $action) ?: []; + } + + /** + * Returns a list of supported actions + * + * @return array + */ + public function getActions() + { + $actions = ArrayUtils::get($this->getConfig(), 'actions'); + if (is_string($actions) && !empty($actions)) { + $actions = json_decode($actions, true); + } + + return array_merge($this->getDefaultActions(), (array)$actions); + } + + /** + * Return a list of the default supported actions + * + * @return array + */ + public function getDefaultActions() + { + return [ + 'contain' => [ + 'options' => [ + 'resizeCanvas' => false, // http://image.intervention.io/api/resizeCanvas + 'position' => 'center', + 'resizeRelative' => false, + 'canvasBackground' => 'ccc', // http://image.intervention.io/getting_started/formats + ] + ], + 'crop' => [ + 'options' => [ + 'position' => 'center', // http://image.intervention.io/api/fit + ] + ] + ]; + } + + /** + * Merge file and thumbnailer config settings and return + * + * @throws Exception + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Parse csv string to an array + * + * @param string $value + * + * @return array + */ + protected function parseCSV($value) + { + if (is_string($value)) { + $value = StringUtils::csv( + $value + ); + } else { + $value = (array)$value; + } + + return $value; + } +} diff --git a/src/core/Directus/Filesystem/composer.json b/src/core/Directus/Filesystem/composer.json new file mode 100644 index 0000000000..119056cdb6 --- /dev/null +++ b/src/core/Directus/Filesystem/composer.json @@ -0,0 +1,24 @@ +{ + "name": "directus/filesystem", + "description": "Directus Filesystem Library", + "keywords": [ + "directus", + "filesystem" + ], + "license": "MIT", + "require": { + "php": ">=5.4.0", + "league/flysystem": "^1.0" + }, + "autoload": { + "psr-4": { + "Directus\\Filesystem\\": "" + } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-d64": "0.9.x-dev" + } + } +} diff --git a/src/core/Directus/Hash/Exception/HasherNotFoundException.php b/src/core/Directus/Hash/Exception/HasherNotFoundException.php new file mode 100644 index 0000000000..6bb78e68f5 --- /dev/null +++ b/src/core/Directus/Hash/Exception/HasherNotFoundException.php @@ -0,0 +1,17 @@ +registerDefaultHashers(); + + foreach ($hashers as $hasher) { + $this->register($hasher); + } + } + + /** + * Hash a given string into the given algorithm + * + * @param string $string + * @param array $options + * + * @return string + */ + public function hash($string, array $options = []) + { + $hasher = ArrayUtils::pull($options, 'hasher', 'core'); + + return $this->get($hasher)->hash($string, $options); + } + + /** + * Verifies whether a given string match a hash in the given algorithm + * + * @param string $string + * @param string $hash + * @param array $options + * + * @return string + */ + public function verify($string, $hash, array $options = []) + { + $hasher = ArrayUtils::pull($options, 'hasher', 'core'); + + return $this->get($hasher)->verify($string, $hash, $options); + } + + /** + * Register a hasher + * + * @param HasherInterface $hasher + */ + public function register(HasherInterface $hasher) + { + $this->hashers[$hasher->getName()] = $hasher; + } + + public function registerDefaultHashers() + { + $hashers = [ + CoreHasher::class, + BCryptHasher::class, + MD5Hasher::class, + Sha1Hasher::class, + Sha224Hasher::class, + Sha256Hasher::class, + Sha384Hasher::class, + Sha512Hasher::class + ]; + + foreach ($hashers as $hasher) { + $this->register(new $hasher()); + } + } + + /** + * @param $name + * + * @return HasherInterface + * + * @throws HasherNotFoundException + */ + public function get($name) + { + $hasher = ArrayUtils::get($this->hashers, $name); + + if (!$hasher) { + throw new HasherNotFoundException($name); + } + + return $hasher; + } +} diff --git a/src/core/Directus/Hash/Hasher/AbstractHashHasher.php b/src/core/Directus/Hash/Hasher/AbstractHashHasher.php new file mode 100644 index 0000000000..05e1d7f4b4 --- /dev/null +++ b/src/core/Directus/Hash/Hasher/AbstractHashHasher.php @@ -0,0 +1,22 @@ +getName(), $string); + } + + /** + * @inheritdoc + */ + public function verify($string, $hash, array $options = []) + { + return hash($this->getName(), $string) === $hash; + } +} diff --git a/src/core/Directus/Hash/Hasher/BCryptHasher.php b/src/core/Directus/Hash/Hasher/BCryptHasher.php new file mode 100644 index 0000000000..8326d08214 --- /dev/null +++ b/src/core/Directus/Hash/Hasher/BCryptHasher.php @@ -0,0 +1,30 @@ +=5.5.0", + "directus/utils": "^1.0.0" + }, + "autoload": { + "psr-4": { + "Directus\\Hash\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-version/6.4": "0.9.x-dev" + } + } +} diff --git a/src/core/Directus/Hook/Emitter.php b/src/core/Directus/Hook/Emitter.php new file mode 100644 index 0000000000..f87dc704aa --- /dev/null +++ b/src/core/Directus/Hook/Emitter.php @@ -0,0 +1,300 @@ +addListener($name, $listener, $priority, self::TYPE_ACTION); + } + + /** + * Add a filter listener wit the given name + * + * @param $name + * @param $listener + * @param int $priority + * + * @return int Listener's index {@see removeListenerWithIndex} + */ + public function addFilter($name, $listener, $priority = self::P_NORMAL) + { + return $this->addListener($name, $listener, $priority, self::TYPE_FILTER); + } + + /** + * Remove listener with a given index + * + * @param $index + */ + public function removeListenerWithIndex($index) + { + $this->listenersList[$index] = null; + } + + /** + * Execute all the the actions listeners registered in the given name + * + * An Action execute the given listener and do not return any value. + * + * @param $name + * @param null $data + */ + public function run($name, $data = null) + { + $listeners = $this->getActionListeners($name); + + $this->executeListeners($listeners, $data, self::TYPE_ACTION); + } + + /** + * @see Emitter->run(); + * + * @param $name + * @param null $data + */ + public function execute($name, $data = null) + { + $this->run($name, $data); + } + + /** + * Execute all the the filters listeners registered in the given name + * + * A Filter execute the given listener and return a modified given value + * + * @param string $name + * @param array $data + * @param array $attributes + * + * @return mixed + */ + public function apply($name, array $data = [], array $attributes = []) + { + $listeners = $this->getFilterListeners($name); + + $payload = new Payload($data, $attributes); + + if ($listeners) { + $payload = $this->executeListeners($listeners, $payload, self::TYPE_FILTER); + } + + return $payload->getData(); + } + + /** + * Get all the actions listeners + * + * @param $name + * + * @return array + */ + public function getActionListeners($name) + { + return $this->getListeners($this->actionListeners, $name); + } + + /** + * Whether the hook action name given has listener or not + * + * @param $name + * + * @return bool + */ + public function hasActionListeners($name) + { + return $this->getActionListeners($name) ? true : false; + } + + /** + * Get all the filters listeners + * + * @param $name + * + * @return array + */ + public function getFilterListeners($name) + { + return $this->getListeners($this->filterListeners, $name); + } + + /** + * Whether the hook filter name given has listener or not + * + * @param $name + * + * @return bool + */ + public function hasFilterListeners($name) + { + return $this->getFilterListeners($name) ? true : false; + } + + /** + * Add a listener + * + * @param $name + * @param $listener + * @param int $priority + * @param int $type + * + * @return int Listener's index {@see removeListenerWithIndex} + */ + protected function addListener($name, $listener, $priority = null, $type = self::TYPE_ACTION) + { + if (is_string($listener) && class_exists($listener)) { + $listener = new $listener(); + } + + if ($priority === null) { + $priority = self::P_NORMAL; + } + + $this->validateListener($listener); + + $index = array_push($this->listenersList, $listener) - 1; + + $arrayName = ($type == self::TYPE_FILTER) ? 'filter' : 'action'; + $this->{$arrayName.'Listeners'}[$name][$priority][] = $index; + + return $index; + } + + /** + * Validate a listener + * + * @param $listener + */ + protected function validateListener($listener) + { + if (!is_callable($listener) && !($listener instanceof HookInterface)) { + throw new \InvalidArgumentException('Listener needs to be a callable or an instance of \Directus\Hook\HookInterface'); + } + } + + /** + * Get all listeners registered into a given name + * + * @param array $items + * @param $name + * + * @return array + */ + protected function getListeners(array $items, $name) + { + $functions = []; + if (array_key_exists($name, $items)) { + $listeners = $items[$name]; + krsort($listeners); + $functions = call_user_func_array('array_merge', $listeners); + } + + return $functions; + } + + /** + * Execute a given listeners list + * + * @param array $listenersIds + * @param null $data + * @param int $listenerType + * + * @return array|mixed|null + */ + protected function executeListeners(array $listenersIds, $data = null, $listenerType = self::TYPE_ACTION) + { + $isFilterType = ($listenerType == self::TYPE_FILTER); + foreach ($listenersIds as $index) { + $listener = $this->listenersList[$index]; + + if ($listener) { + if ($listener instanceof HookInterface) { + $listener = [$listener, 'handle']; + } + + if (!is_array($data)) { + $data = [$data]; + } + + $returnedValue = call_user_func_array($listener, $data); + if ($isFilterType) { + $data = $returnedValue; + } + } + } + + return ($isFilterType ? $data : null); + } +} diff --git a/src/core/Directus/Hook/HookInterface.php b/src/core/Directus/Hook/HookInterface.php new file mode 100644 index 0000000000..214e34544d --- /dev/null +++ b/src/core/Directus/Hook/HookInterface.php @@ -0,0 +1,13 @@ +attributes = new Collection($attributes); + } + + /** + * Gets an attribute + * + * @param $key + * + * @return mixed + */ + public function attribute($key) + { + return $this->attributes[$key]; + } + + /** + * @return Collection + */ + public function attributes() + { + return $this->attributes; + } + + /** + * Gets all the data + * + * @return array + */ + public function getData() + { + return $this->items; + } +} diff --git a/src/core/Directus/Hook/README.md b/src/core/Directus/Hook/README.md new file mode 100644 index 0000000000..c86359af5d --- /dev/null +++ b/src/core/Directus/Hook/README.md @@ -0,0 +1 @@ +# Directus Hook diff --git a/src/core/Directus/Hook/composer.json b/src/core/Directus/Hook/composer.json new file mode 100644 index 0000000000..fadf8070bf --- /dev/null +++ b/src/core/Directus/Hook/composer.json @@ -0,0 +1,23 @@ +{ + "name": "directus/hook", + "description": "Directus Hook Library", + "keywords": [ + "directus", + "hook", + "event" + ], + "license": "MIT", + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "Directus\\Hook\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-d64": "0.9.x-dev" + } + } +} diff --git a/src/core/Directus/Mail/Exception/InvalidTransportException.php b/src/core/Directus/Mail/Exception/InvalidTransportException.php new file mode 100644 index 0000000000..7b0128fd60 --- /dev/null +++ b/src/core/Directus/Mail/Exception/InvalidTransportException.php @@ -0,0 +1,24 @@ +transports = $transportManager; + } + + /** + * Creates a new message instance + * + * @return Message + */ + public function createMessage() + { + return new Message(); + } + + public function send($view, array $data, \Closure $callback = null) + { + $transport = $this->transports->getDefault(); + $message = $this->createMessage(); + + // Get global information + $config = $transport->getConfig(); + if ($config->has('from')) { + $message->setFrom($config->get('from')); + } + + if ($config->has('bcc')) { + $message->setBcc($config->get('bcc')); + } + + if ($config->has('cc')) { + $message->setCc($config->get('cc')); + } + + $content = parse_twig($view, array_merge( + $data, + ['api' => ['env' => get_api_env()]] + )); + + $message->setBody($content, 'text/html'); + + if ($callback) { + call_user_func($callback, $message); + } + + if (!array_key_exists($transport->getName(), $this->mailers)) { + $this->mailers[$transport->getName()] = new \Swift_Mailer($transport); + } + + $swiftMailer = $this->mailers[$transport->getName()]; + $swiftMailer->send($message); + } +} diff --git a/src/core/Directus/Mail/Message.php b/src/core/Directus/Mail/Message.php new file mode 100644 index 0000000000..9a8c4b0f45 --- /dev/null +++ b/src/core/Directus/Mail/Message.php @@ -0,0 +1,8 @@ +transports[$name] = $transport; + $this->config[$name] = $config; + } + + /** + * @param $name + * + * @return AbstractTransport + */ + public function get($name) + { + if (!isset($this->instances[$name])) { + $this->instances[$name] = $this->build($name, ArrayUtils::get($this->config, $name, [])); + } + + return $this->instances[$name]; + } + + /** + * Gets the first or "default" adapter + * + * @return AbstractTransport|null + */ + public function getDefault() + { + $instance = null; + if (array_key_exists('default', $this->transports)) { + $instance = $this->get('default'); + } else if (count($this->transports) > 0) { + $instance = $this->get($this->transports[0]); + } + + return $instance; + } + + /** + * Creates a instance of a transport registered with the given name + * + * @param string $name + * @param array $config + * + * @return AbstractTransport + */ + protected function build($name, array $config = []) + { + if (!array_key_exists($name, $this->transports)) { + throw new TransportNotFoundException($name); + } + + $transport = $this->transports[$name]; + if (!is_string($transport) && !is_object($transport) && !is_callable($transport)) { + throw new InvalidTransportException($this->transports[$name]); + } + if (is_string($transport) && !class_exists($transport)) { + throw new InvalidTransportException($this->transports[$name]); + } + + if (is_callable($transport)) { + $instance = call_user_func($transport); + } else if (is_string($transport)) { + $instance = new $transport($config); + } else { + $instance = $transport; + } + + if (!($instance instanceof AbstractTransport)) { + throw new RuntimeException( + sprintf('%s is not an instance of %s', $instance, AbstractTransport::class) + ); + } + + return $instance; + } +} diff --git a/src/core/Directus/Mail/Transports/AbstractTransport.php b/src/core/Directus/Mail/Transports/AbstractTransport.php new file mode 100644 index 0000000000..ae119f335a --- /dev/null +++ b/src/core/Directus/Mail/Transports/AbstractTransport.php @@ -0,0 +1,80 @@ +name = $name; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return Collection + */ + public function getConfig() + { + return $this->config; + } + + /** + * @inheritDoc + */ + public function isStarted() + { + return true; + } + + /** + * @inheritDoc + */ + public function start() + { + return true; + } + + /** + * @inheritDoc + */ + public function stop() + { + return true; + } + + /** + * @inheritDoc + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + foreach ($this->listeners as $listener) { + if ($listener === $plugin) { + return; + } + } + + $this->listeners[] = $plugin; + } +} diff --git a/src/core/Directus/Mail/Transports/SendMailTransport.php b/src/core/Directus/Mail/Transports/SendMailTransport.php new file mode 100644 index 0000000000..ef363e8256 --- /dev/null +++ b/src/core/Directus/Mail/Transports/SendMailTransport.php @@ -0,0 +1,28 @@ +config = new Collection($config); + + $this->sendmail = \Swift_SendmailTransport::newInstance($this->config->get('sendmail')); + } + + /** + * @inheritdoc + */ + public function send(\Swift_Mime_Message $message, &$failedRecipients = null) + { + return $this->sendmail->send($message, &$failedRecipients); + } +} diff --git a/src/core/Directus/Mail/Transports/SimpleFileTransport.php b/src/core/Directus/Mail/Transports/SimpleFileTransport.php new file mode 100644 index 0000000000..58269d5e1a --- /dev/null +++ b/src/core/Directus/Mail/Transports/SimpleFileTransport.php @@ -0,0 +1,34 @@ +config = new Collection($config); + } + + /** + * @inheritdoc + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $path = rtrim($this->config->get('path', ''), '/') . '/' . time() . '.txt'; + + $message = [ + $message->getSubject(), + $message->getBody() + ]; + + file_put_contents($path, implode("\n", $message)); + } +} diff --git a/src/core/Directus/Mail/Transports/SmtpTransport.php b/src/core/Directus/Mail/Transports/SmtpTransport.php new file mode 100644 index 0000000000..e9efe0f043 --- /dev/null +++ b/src/core/Directus/Mail/Transports/SmtpTransport.php @@ -0,0 +1,44 @@ +config = new Collection($config); + $transport = \Swift_SmtpTransport::newInstance( + $this->config->get('host'), + $this->config->get('port') + ); + + if ($this->config->has('username')) { + $transport->setUsername($this->config->get('username')); + } + + if ($this->config->has('password')) { + $transport->setPassword($this->config->get('password')); + } + + if ($this->config->has('encryption')) { + $transport->setEncryption($this->config->get('encryption')); + } + + $this->smtp = $transport; + } + + /** + * @inheritdoc + */ + public function send(\Swift_Mime_Message $message, &$failedRecipients = null) + { + return $this->smtp->send($message, &$failedRecipients); + } +} diff --git a/src/core/Directus/Permissions/Acl.php b/src/core/Directus/Permissions/Acl.php new file mode 100644 index 0000000000..1ee737f08e --- /dev/null +++ b/src/core/Directus/Permissions/Acl.php @@ -0,0 +1,1102 @@ + self::LEVEL_FULL, + self::ACTION_READ => self::LEVEL_FULL, + self::ACTION_UPDATE => self::LEVEL_FULL, + self::ACTION_DELETE => self::LEVEL_FULL + ]; + + const PERMISSION_NONE = [ + self::ACTION_CREATE => self::LEVEL_NONE, + self::ACTION_READ => self::LEVEL_NONE, + self::ACTION_UPDATE => self::LEVEL_NONE, + self::ACTION_DELETE => self::LEVEL_NONE + ]; + + const PERMISSION_READ = [ + self::ACTION_CREATE => self::LEVEL_NONE, + self::ACTION_READ => self::LEVEL_FULL, + self::ACTION_UPDATE => self::LEVEL_NONE, + self::ACTION_DELETE => self::LEVEL_NONE + ]; + + const PERMISSION_WRITE = [ + self::ACTION_CREATE => self::LEVEL_FULL, + self::ACTION_READ => self::LEVEL_NONE, + self::ACTION_UPDATE => self::LEVEL_FULL, + self::ACTION_DELETE => self::LEVEL_NONE + ]; + + const PERMISSION_READ_WRITE = [ + self::ACTION_CREATE => self::LEVEL_FULL, + self::ACTION_READ => self::LEVEL_FULL, + self::ACTION_UPDATE => self::LEVEL_FULL, + self::ACTION_DELETE => 0 + ]; + + protected $permissionLevelsMapping = [ + 'none' => 0, + 'user' => 1, + 'group' => 2, + 'full' => 3 + ]; + + /** + * Permissions by status grouped by collection + * + * @var array + */ + protected $statusPermissions = []; + + /** + * Permissions grouped by collection + * + * @var array + */ + protected $globalPermissions = []; + + /** + * Authenticated user id + * + * @var int|null + */ + protected $userId = null; + + /** + * List of roles id the user beings to + * + * @var array + */ + protected $roleIds = []; + + /** + * List of allowed IPs by role + * + * @var array + */ + protected $rolesIpWhitelist = []; + + /** + * Flag to determine whether the user is public or not + * + * @var bool + */ + protected $isPublic = null; + + public function __construct(array $permissions = []) + { + $this->setPermissions($permissions); + } + + /** + * Sets the authenticated user id + * + * @param $userId + */ + public function setUserId($userId) + { + $this->userId = (int)$userId; + } + + /** + * Sets whether the authenticated user is public + * + * @param $public + */ + public function setPublic($public) + { + $this->isPublic = (bool)$public; + } + + /** + * Gets the authenticated user id + * + * @return int|null + */ + public function getUserId() + { + return $this->userId; + } + + /** + * Gets whether the authenticated user is public + * + * @return bool + */ + public function isPublic() + { + return $this->isPublic === true; + } + + /** + * Gets whether the authenticated user is admin + * + * @return bool + */ + public function isAdmin() + { + return $this->hasAdminRole(); + } + + /** + * Checks whether or not the user has admin role + * + * @return bool + */ + public function hasAdminRole() + { + return $this->hasRole(1); + } + + /** + * Checks whether or not the user has the given role id + * + * @param int $roleId + * + * @return bool + */ + public function hasRole($roleId) + { + return in_array($roleId, $this->getRolesId()); + } + + /** + * Get all role IDs + * + * @return array + */ + public function getRolesId() + { + return $this->roleIds; + } + + /** + * Sets the user roles ip whitelist + * + * @param array $rolesIpWhitelist + */ + public function setRolesIpWhitelist(array $rolesIpWhitelist) + { + foreach ($rolesIpWhitelist as $role => $ipList) { + if (!is_array($ipList)) { + $ipList = explode(',', $ipList); + } + + $this->rolesIpWhitelist[$role] = $ipList; + } + } + + /** + * Checks whether or not the given ip is allowed in one of the roles + * + * @param $ip + * + * @return bool + */ + public function isIpAllowed($ip) + { + $allowed = true; + foreach ($this->rolesIpWhitelist as $list) { + if (!empty($list) && !in_array($ip, $list)) { + $allowed = false; + break; + } + } + + return $allowed; + } + + /** + * Sets the group permissions + * + * @param array $permissions + * + * @return $this + */ + public function setPermissions(array $permissions) + { + foreach ($permissions as $collection => $collectionPermissions) { + foreach ($collectionPermissions as $permission) { + $roleId = ArrayUtils::get($permission, 'role'); + + if (!in_array($roleId, $this->roleIds)) { + $this->roleIds[] = $roleId; + } + } + + $this->setCollectionPermissions($collection, $collectionPermissions); + } + + return $this; + } + + /** + * Sets permissions to the given collection + * + * @param string $collection + * @param array $permissions + */ + public function setCollectionPermissions($collection, array $permissions) + { + foreach ($permissions as $permission) { + $this->setCollectionPermission($collection, $permission); + } + } + + /** + * Sets a collection permission + * + * @param $collection + * @param array $permission + * + * @return $this + */ + public function setCollectionPermission($collection, array $permission) + { + $status = ArrayUtils::get($permission, 'status'); + + if (is_null($status) && !isset($this->globalPermissions[$collection])) { + $this->globalPermissions[$collection] = $permission; + } else if (!is_null($status) && !isset($this->statusPermissions[$collection][$status])) { + $this->statusPermissions[$collection][$status] = $permission; + unset($this->globalPermissions[$collection]); + } + + return $this; + } + + /** + * Gets the group permissions + * + * @return array + */ + public function getPermissions() + { + return array_merge($this->globalPermissions, $this->statusPermissions); + } + + public function getCollectionStatuses($collection) + { + $statuses = null; + $permissions = ArrayUtils::get($this->statusPermissions, $collection); + if (!empty($permissions)) { + $statuses = array_keys($permissions); + } + + return $statuses; + } + + /** + * Gets a collection permissions + * + * @param string $collection + * + * @return array + */ + public function getCollectionPermissions($collection) + { + if (array_key_exists($collection, $this->statusPermissions)) { + return $this->statusPermissions[$collection]; + } else if (array_key_exists($collection, $this->globalPermissions)) { + return $this->globalPermissions[$collection]; + } + + return []; + } + + /** + * Gets a collection permission + * + * @param string $collection + * @param null|int|string $status + * + * @return array + */ + public function getPermission($collection, $status = null) + { + $permissions = $this->getCollectionPermissions($collection); + $hasStatusPermissions = array_key_exists($collection, $this->statusPermissions); + + if (is_null($status) && $hasStatusPermissions) { + $permissions = []; + } else if ($hasStatusPermissions) { + $permissions = ArrayUtils::get($permissions, $status, []); + } + + return $permissions; + } + + /** + * Gets the given type (read/write) field blacklist + * + * @param string $type + * @param string $collection + * @param mixed $status + * + * @return array + */ + public function getFieldBlacklist($type, $collection, $status = null) + { + $permission = $this->getPermission($collection, $status); + + switch ($type) { + case static::FIELD_READ_BLACKLIST: + $fields = ArrayUtils::get($permission, static::FIELD_READ_BLACKLIST); + break; + case static::FIELD_WRITE_BLACKLIST: + $fields = ArrayUtils::get($permission, static::FIELD_WRITE_BLACKLIST); + break; + default: + $fields = []; + } + + return $fields ?: []; + } + + /** + * Gets the read field blacklist + * + * @param string $collection + * @param mixed $status + * + * @return array + */ + public function getReadFieldBlacklist($collection, $status = null) + { + return $this->getFieldBlacklist(static::FIELD_READ_BLACKLIST, $collection, $status); + } + + /** + * Gets the write field blacklist + * + * @param string $collection + * @param mixed $status + * + * @return array|mixed + */ + public function getWriteFieldBlacklist($collection, $status = null) + { + return $this->getFieldBlacklist(static::FIELD_WRITE_BLACKLIST, $collection, $status); + } + + /** + * Checks whether the user can add an item in the given collection + * + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function canCreate($collection, $status = null) + { + return $this->allowTo(static::ACTION_CREATE, static::LEVEL_USER, $collection, $status); + } + + /** + * Checks whether the user can view an item in the given collection + * + * @param int $level + * @param $collection + * @param string|int|null $status + * + * @return bool + */ + public function canReadAt($level, $collection, $status = null) + { + return $this->allowTo(static::ACTION_READ, $level, $collection, $status); + } + + /** + * Checks whether the user can read at least their own items in the given collection + * + * @param $collection + * @param string|int|null $status + * + * @return bool + */ + public function canRead($collection, $status = null) + { + return $this->canReadMine($collection, $status); + } + + /** + * Checks whether the user can read at least in one permission level no matter the status + * + * @param string $collection + * + * @return bool + */ + public function canReadOnce($collection) + { + return $this->allowToOnce(static::ACTION_READ, $collection); + } + + /** + * Checks whether the user can read their own items in the given collection + * + * @param $collection + * @param string|int|null $status + * + * @return bool + */ + public function canReadMine($collection, $status = null) + { + return $this->canReadAt(static::LEVEL_USER, $collection, $status); + } + + /** + * Checks whether the user can read same group users items in the given collection + * + * @param $collection + * @param string|int|null $status + * + * @return bool + */ + public function canReadFromGroup($collection, $status = null) + { + return $this->canReadAt(static::LEVEL_GROUP, $collection, $status); + } + + /** + * Checks whether the user can read same group users items in the given collection + * + * @param $collection + * @param string|int|null $status + * + * @return bool + */ + public function canReadAll($collection, $status = null) + { + return $this->canReadAt(static::LEVEL_FULL, $collection, $status); + } + + /** + * Checks whether the user can update an item in the given collection + * + * @param int $level + * @param string $collection + * @param mixed $status + * + * @return bool + */ + public function canUpdateAt($level, $collection, $status = null) + { + return $this->allowTo(static::ACTION_UPDATE, $level, $collection, $status); + } + + /** + * Checks whether the user can update at least their own items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @return bool + */ + public function canUpdate($collection, $status = null) + { + return $this->canUpdateMine($collection, $status); + } + + /** + * Checks whether the user can update their own items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @return bool + */ + public function canUpdateMine($collection, $status = null) + { + return $this->canUpdateAt(static::LEVEL_USER, $collection, $status); + } + + /** + * Checks whether the user can update items from the same user groups in the given collection + * + * @param string $collection + * @param mixed $status + * + * @return bool + */ + public function canUpdateFromGroup($collection, $status = null) + { + return $this->canUpdateAt(static::LEVEL_GROUP, $collection, $status); + } + + /** + * Checks whether the user can update all items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @return bool + */ + public function canUpdateAll($collection, $status = null) + { + return $this->canUpdateAt(static::LEVEL_FULL, $collection, $status); + } + + /** + * Checks whether the user can delete an item in the given collection + * + * @param int $level + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function canDeleteAt($level, $collection, $status = null) + { + return $this->allowTo(static::ACTION_DELETE, $level, $collection, $status); + } + + /** + * Checks whether the user can delete at least their own items in the given collection + * + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function canDelete($collection, $status = null) + { + return $this->canDeleteMine($collection, $status); + } + + /** + * Checks whether the user can delete its own items in the given collection + * + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function canDeleteMine($collection, $status = null) + { + return $this->canDeleteAt(static::LEVEL_USER, $collection, $status); + } + + /** + * Checks whether the user can delete items that belongs to a user in the same group in the given collection + * + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function canDeleteFromGroup($collection, $status = null) + { + return $this->canDeleteAt(static::LEVEL_GROUP, $collection, $status); + } + + /** + * Checks whether the user can delete any items in the given collection + * + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function canDeleteAll($collection, $status = null) + { + return $this->canDeleteAt(static::LEVEL_FULL, $collection, $status); + } + + /** + * Checks whether the user can alter the given table + * + * @param $collection + * + * @return bool + */ + public function canAlter($collection) + { + return $this->isAdmin(); + } + + /** + * Checks whether a given collection requires explanation message + * + * @param string $collection + * @param string|int|null $status + * + * @return bool + */ + public function requireComment($collection, $status = null) + { + $permission = $this->getPermission($collection, $status); + if (!array_key_exists('comment', $permission)) { + return false; + } + + return $permission['comment'] === 'explain'; + } + + /** + * Checks whether a given collection allows to add comments + * + * @param string $collection + * @param null $status + * + * @return bool + */ + public function canComment($collection, $status = null) + { + $permission = $this->getPermission($collection, $status); + if (!array_key_exists('comment', $permission)) { + return false; + } + + return in_array(strtolower($permission['comment']), ['explain', 'comment']); + } + + /** + * Throws an exception if the user cannot read their own items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionReadException + */ + public function enforceReadMine($collection, $status = null) + { + if (!$this->canReadMine($collection, $status)) { + throw new Exception\ForbiddenCollectionReadException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot read the same group items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionReadException + */ + public function enforceReadFromGroup($collection, $status = null) + { + if (!$this->canReadFromGroup($collection, $status)) { + throw new Exception\ForbiddenCollectionReadException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot read all items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionReadException + */ + public function enforceReadAll($collection, $status = null) + { + if (!$this->canReadAll($collection, $status)) { + throw new Exception\ForbiddenCollectionReadException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot create a item in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionReadException + */ + public function enforceRead($collection, $status = null) + { + $this->enforceReadMine($collection, $status); + } + + /** + * Throws an exception if the user cannot read a item in any level or status + * + * @param string $collection + * + * @throws Exception\ForbiddenCollectionReadException + */ + public function enforceReadOnce($collection) + { + if (!$this->canReadOnce($collection)) { + throw new Exception\ForbiddenCollectionReadException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot create a item in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionCreateException + */ + public function enforceCreate($collection, $status = null) + { + if (!$this->canCreate($collection, $status)) { + throw new Exception\ForbiddenCollectionCreateException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot alter the given collection + * + * @param $collection + * + * @throws Exception\ForbiddenCollectionAlterException + */ + public function enforceAlter($collection) + { + if (!$this->canAlter($collection)) { + throw new Exception\ForbiddenCollectionAlterException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot update their own items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionUpdateException + */ + public function enforceUpdateMine($collection, $status = null) + { + if (!$this->canUpdateMine($collection, $status)) { + throw new Exception\ForbiddenCollectionUpdateException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot update items from the same group in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionUpdateException + */ + public function enforceUpdateFromGroup($collection, $status = null) + { + if (!$this->canUpdateFromGroup($collection, $status)) { + throw new Exception\ForbiddenCollectionUpdateException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot update all items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionUpdateException + */ + public function enforceUpdateAll($collection, $status = null) + { + if (!$this->canUpdateAll($collection, $status)) { + throw new Exception\ForbiddenCollectionUpdateException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot update an item in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionUpdateException + */ + public function enforceUpdate($collection, $status = null) + { + $this->enforceUpdateMine($collection, $status); + } + + /** + * Throws an exception if the user cannot delete their own items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionDeleteException + */ + public function enforceDeleteMine($collection, $status = null) + { + if (!$this->canDeleteMine($collection, $status)) { + throw new Exception\ForbiddenCollectionDeleteException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot delete items from the same group in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionDeleteException + */ + public function enforceDeleteFromGroup($collection, $status = null) + { + if (!$this->canDeleteFromGroup($collection, $status)) { + throw new Exception\ForbiddenCollectionDeleteException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot delete all items in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionDeleteException + */ + public function enforceDeleteAll($collection, $status = null) + { + if (!$this->canDeleteAll($collection, $status)) { + throw new Exception\ForbiddenCollectionDeleteException( + $collection + ); + } + } + + /** + * Throws an exception if the user cannot delete an item in the given collection + * + * @param string $collection + * @param mixed $status + * + * @throws Exception\ForbiddenCollectionDeleteException + */ + public function enforceDelete($collection, $status = null) + { + $this->enforceDeleteMine($collection, $status); + } + + /** + * Checks whether the user can see the given column + * + * @param string $collection + * @param string $field + * @param null|string|int $status + * + * @return bool + */ + public function canReadField($collection, $field, $status = null) + { + $fields = $this->getReadFieldBlacklist($collection, $status); + + return !in_array($field, $fields); + } + + /** + * Checks whether the user can see the given column + * + * @param string $collection + * @param string $field + * @param null|int|string $status + * + * @return bool + */ + public function canWriteField($collection, $field, $status = null) + { + $fields = $this->getWriteFieldBlacklist($collection, $status); + + return !in_array($field, $fields); + } + + /** + * Throws an exception if the user has not permission to read from the given field + * + * @param string $collection + * @param string|array $fields + * @param null|int|string $status + * + * @throws ForbiddenFieldReadException + */ + public function enforceReadField($collection, $fields, $status = null) + { + if (!is_array($fields)) { + $fields = [$fields]; + } + + foreach ($fields as $field) { + if (!$this->canReadField($collection, $field, $status)) { + throw new ForbiddenFieldReadException($collection, $field); + } + } + } + + /** + * Throws an exception if the user has not permission to write to the given field + * + * @param string $collection + * @param string|array $fields + * @param null|int|string $status + * + * @throws ForbiddenFieldWriteException + */ + public function enforceWriteField($collection, $fields, $status = null) + { + if (!is_array($fields)) { + $fields = [$fields]; + } + + foreach ($fields as $field) { + if (!$this->canWriteField($collection, $field, $status)) { + throw new ForbiddenFieldWriteException($collection, $field); + } + } + } + + /** + * Given table name $table and privilege constant $privilege, return boolean + * value indicating whether the current user group has permission to perform + * the specified table-level action on the specified table. + * + * @param string $action + * @param string $collection + * @param int $level + * @param mixed $status + * + * @return boolean + */ + public function allowTo($action, $level, $collection, $status = null) + { + if ($this->isAdmin()) { + return true; + } + + $permission = $this->getPermission($collection, $status); + $permissionLevel = ArrayUtils::get($permission, $action); + + return $this->can($permissionLevel, $level); + } + + public function allowToOnce($action, $collection) + { + if ($this->isAdmin()) { + return true; + } + + $permissions = []; + if (array_key_exists($collection, $this->statusPermissions)) { + $permissions = $this->statusPermissions[$collection]; + } else if (array_key_exists($collection, $this->globalPermissions)) { + $permissions = [$this->globalPermissions[$collection]]; + } + + $allowed = false; + foreach ($permissions as $permission) { + $permissionLevel = ArrayUtils::get($permission, $action); + + if ($this->can($permissionLevel, static::LEVEL_USER)) { + $allowed = true; + break; + } + } + + return $allowed; + } + + /** + * Returns a list of status the given collection has permission to read + * + * @param string $collection + * + * @return array|mixed + */ + public function getCollectionStatusesReadPermission($collection) + { + if ($this->isAdmin()) { + return null; + } + + $statuses = false; + + if (array_key_exists($collection, $this->statusPermissions)) { + $statuses = []; + + foreach ($this->statusPermissions[$collection] as $status => $permission) { + $permissionLevel = ArrayUtils::get($permission, static::ACTION_READ); + + if ($this->can($permissionLevel, static::LEVEL_USER)) { + $statuses[] = $status; + } + } + } else if (array_key_exists($collection, $this->globalPermissions)) { + $permission = $this->globalPermissions[$collection]; + $permissionLevel = ArrayUtils::get($permission, static::ACTION_READ); + + if ($this->can($permissionLevel, static::LEVEL_USER)) { + $statuses = null; + } + } + + return $statuses; + } + + /** + * Checks whether or not a permission level has equal or higher level + * + * @param string $permissionLevel + * @param string $level + * + * @return bool + */ + protected function can($permissionLevel, $level) + { + if (!$permissionLevel) { + return false; + } + + $levelValue = ArrayUtils::get($this->permissionLevelsMapping, $level); + $permissionLevelValue = ArrayUtils::get($this->permissionLevelsMapping, $permissionLevel); + + if ($levelValue && $permissionLevelValue) { + return $levelValue <= $permissionLevelValue; + } + + return false; + } +} diff --git a/src/core/Directus/Permissions/Exception/ForbiddenCollectionAlterException.php b/src/core/Directus/Permissions/Exception/ForbiddenCollectionAlterException.php new file mode 100644 index 0000000000..4f79e9b54c --- /dev/null +++ b/src/core/Directus/Permissions/Exception/ForbiddenCollectionAlterException.php @@ -0,0 +1,18 @@ +=5.4.0", + "directus/exception": "~2.0.0" + }, + "autoload": { + "psr-4": { + "Directus\\Permissions\\": "" + } + } +} diff --git a/src/core/Directus/Services/AbstractExtensionsController.php b/src/core/Directus/Services/AbstractExtensionsController.php new file mode 100644 index 0000000000..d05b721a61 --- /dev/null +++ b/src/core/Directus/Services/AbstractExtensionsController.php @@ -0,0 +1,46 @@ + $addOns]; + } + + $directusBasePath = $this->container->get('path_base'); + + $filePaths = find_directories($basePath); + foreach ($filePaths as $path) { + $path .= '/meta.json'; + + if (!file_exists($path)) { + continue; + } + + $addOnsPath = trim(substr($path, strlen($basePath)), '/'); + $data = [ + 'id' => basename(dirname($addOnsPath)), + // NOTE: This is a temporary solution until we implement core config + // In this case /public is the public root path + 'path' => trim(substr($path, strlen($directusBasePath) + strlen('/public')), '/') + ]; + + $meta = @json_decode(file_get_contents($path), true); + if ($meta) { + unset($meta['id']); + $data = array_merge($data, $meta); + } + + $addOns[] = $data; + } + + return ['data' => $addOns]; + } +} diff --git a/src/core/Directus/Services/AbstractService.php b/src/core/Directus/Services/AbstractService.php new file mode 100644 index 0000000000..165b4974c3 --- /dev/null +++ b/src/core/Directus/Services/AbstractService.php @@ -0,0 +1,467 @@ +container = $container; + $this->validator = new Validator(); + } + + /** + * Gets application container + * + * @return Container + */ + protected function getContainer() + { + return $this->container; + } + + /** + * Gets application db connection instance + * + * @return \Zend\Db\Adapter\Adapter + */ + protected function getConnection() + { + return $this->getContainer()->get('database'); + } + + /** + * Gets schema manager instance + * + * @return SchemaManager + */ + public function getSchemaManager() + { + return $this->getContainer()->get('schema_manager'); + } + + /** + * @param $name + * @param $acl + * + * @return RelationalTableGateway + */ + public function createTableGateway($name, $acl = true) + { + return TableGatewayFactory::create($name, [ + 'acl' => $acl !== false ? $this->getAcl() : false, + 'connection' => $this->getConnection() + ]); + } + + /** + * Gets Acl instance + * + * @return Acl + */ + protected function getAcl() + { + return $this->getContainer()->get('acl'); + } + + /** + * Validates a given data against a constraint + * + * @param array $data + * @param array $constraints + * + * @throws BadRequestException + */ + public function validate(array $data, array $constraints) + { + $constraintViolations = $this->getViolations($data, $constraints); + + $this->throwErrorIfAny($constraintViolations); + } + + /** + * + * + * @param string $name + * + * @throws ForbiddenSystemTableDirectAccessException + */ + public function throwErrorIfSystemTable($name) + { + /** @var SchemaManager $schemaManager */ + $schemaManager = $this->container->get('schema_manager'); + if (in_array($name, $schemaManager->getSystemCollections())) { + throw new ForbiddenSystemTableDirectAccessException($name); + } + } + + /** + * @param array $data + * @param array $constraints + * + * @return array + */ + protected function getViolations(array $data, array $constraints) + { + $violations = []; + + foreach ($constraints as $field => $constraint) { + if (is_string($constraint)) { + $constraint = explode('|', $constraint); + } + + $violations[$field] = $this->validator->validate(ArrayUtils::get($data, $field), $constraint); + } + + return $violations; + } + + /** + * Throws an exception if any violations was made + * + * @param ConstraintViolationList[] $violations + * + * @throws BadRequestException + */ + protected function throwErrorIfAny(array $violations) + { + $results = []; + + /** @var ConstraintViolationList $violation */ + foreach ($violations as $field => $violation) { + $iterator = $violation->getIterator(); + + $errors = []; + while ($iterator->valid()) { + $constraintViolation = $iterator->current(); + $errors[] = $constraintViolation->getMessage(); + $iterator->next(); + } + + if ($errors) { + $results[] = sprintf('%s: %s', $field, implode(', ', $errors)); + } + } + + if (count($results) > 0) { + throw new InvalidRequestException(implode(' ', $results)); + } + } + + /** + * Creates the constraint for a an specific table columns + * + * @param string $collectionName + * @param array $fields List of columns name + * + * @return array + */ + protected function createConstraintFor($collectionName, array $fields = []) + { + /** @var SchemaManager $schemaManager */ + $schemaManager = $this->container->get('schema_manager'); + $collectionObject = $schemaManager->getCollection($collectionName); + + $constraints = []; + + if ($fields === null) { + return $constraints; + } + + foreach ($collectionObject->getFields($fields) as $field) { + $columnConstraints = []; + + if ($field->hasAutoIncrement()) { + continue; + } + + $isRequired = $field->isRequired(); + $isStatusField = $field->isStatusType(); + if (!$isRequired && $isStatusField && $field->getDefaultValue() === null) { + $isRequired = true; + } + + if ($isRequired || (!$field->isNullable() && $field->getDefaultValue() == null)) { + $columnConstraints[] = 'required'; + } + + if ($field->isArray()) { + $columnConstraints[] = 'array'; + } else if ($field->isJson()) { + $columnConstraints[] = 'json'; + } + // TODO: Relational accept its type, null (if allowed) and a object + // else if ($schemaManager->isNumericType($field->getType())) { + // $columnConstraints[] = 'numeric'; + // } else if ($schemaManager->isStringType($field->getType())) { + // $columnConstraints[] = 'string'; + // } + + if (!empty($columnConstraints)) { + $constraints[$field->getName()] = $columnConstraints; + } + } + + return $constraints; + } + + protected function tagResponseCache($tags) + { + $this->container->get('response_cache')->tag($tags); + } + + protected function invalidateCacheTags($tags) + { + $this->container->get('cache')->getPool()->invalidateTags($tags); + } + + /** + * @param RelationalTableGateway $gateway + * @param array $params + * @param \Closure|null $queryCallback + * + * @return array|mixed + */ + protected function getItemsAndSetResponseCacheTags(RelationalTableGateway $gateway, array $params, \Closure $queryCallback = null) + { + return $this->getDataAndSetResponseCacheTags([$gateway, 'getItems'], [$params, $queryCallback]); + } + + /** + * @param RelationalTableGateway $gateway + * @param string|int|array + * @param array $params + * + * @return array|mixed + */ + protected function getItemsByIdsAndSetResponseCacheTags(RelationalTableGateway $gateway, $ids, array $params) + { + return $this->getDataAndSetResponseCacheTags([$gateway, 'getItemsByIds'], [$ids, $params]); + } + + /** + * @param callable $callable + * @param array $callableParams + * @param null $pkName + * @return array|mixed + */ + protected function getDataAndSetResponseCacheTags(Callable $callable, array $callableParams = [], $pkName = null) + { + $container = $this->container; + + if (is_array($callable) && $callable[0] instanceof RelationalTableGateway) { + /** @var $callable[0] RelationalTableGateway */ + $pkName = $callable[0]->primaryKeyFieldName; + } + + $setIdTags = function(Payload $payload) use($pkName, $container) { + $collectionName = $payload->attribute('collection_name'); + + $this->tagResponseCache('table_'.$collectionName); + // Note: See other reference to permissions_collection_<> + // to proper set a new tag now that group doesn't exists anymore + $this->tagResponseCache('permissions_collection_'.$collectionName); + + foreach ($payload->getData() as $item) { + $this->tagResponseCache('entity_'.$collectionName.'_'.$item[$pkName]); + } + + return $payload; + }; + + /** @var Emitter $hookEmitter */ + $hookEmitter = $container->get('hook_emitter'); + + $listenerId = $hookEmitter->addFilter('collection.select', $setIdTags, Emitter::P_LOW); + $result = call_user_func_array($callable, $callableParams); + $hookEmitter->removeListenerWithIndex($listenerId); + + return $result; + } + + protected function getCRUDParams(array $params) + { + $activityLoggingDisabled = ArrayUtils::get($params, 'activity_skip', 0) == 1; + $activityMode = $activityLoggingDisabled + ? RelationalTableGateway::ACTIVITY_ENTRY_MODE_DISABLED + : RelationalTableGateway::ACTIVITY_ENTRY_MODE_PARENT; + + return [ + 'activity_mode' => $activityMode, + 'activity_comment' => ArrayUtils::get($params, 'comment') + ]; + } + + /** + * Validates the payload against a collection fields + * + * @param string $collectionName + * @param array|null $fields + * @param array $payload + * @param array $params + * + * @throws BadRequestException + */ + protected function validatePayload($collectionName, $fields, array $payload, array $params) + { + $collection = $this->getSchemaManager()->getCollection($collectionName); + $payloadCount = count($payload); + $hasPrimaryKeyData = ArrayUtils::has($payload, $collection->getPrimaryKeyName()); + + if ($payloadCount === 0 || ($hasPrimaryKeyData && count($payload) === 1)) { + throw new BadRequestException('Payload cannot be empty'); + } + + $columnsToValidate = []; + + // TODO: Validate empty request + // If the user PATCH, POST or PUT with empty body, must throw an exception to avoid continue the execution + // with the exception of POST, that can use the default value instead + // TODO: Crate a email interface for the sake of validation + if (is_array($fields)) { + $columnsToValidate = $fields; + } + + $this->validatePayloadFields($collectionName, $payload); + // TODO: Ideally this should be part of the validator constraints + // we need to accept options for the constraint builder + $this->validatePayloadWithFieldsValidation($collectionName, $payload); + + $this->validate($payload, $this->createConstraintFor($collectionName, $columnsToValidate)); + } + + /** + * Verify that the payload has its primary key otherwise an exception will be thrown + * + * @param $collectionName + * @param array $payload + * + * @throws BadRequestException + */ + protected function validatePayloadHasPrimaryKey($collectionName, array $payload) + { + $collection = $this->getSchemaManager()->getCollection($collectionName); + $primaryKey = $collection->getPrimaryKeyName(); + + if (!ArrayUtils::has($payload, $primaryKey) || !$payload[$primaryKey]) { + throw new BadRequestException('Payload must include the primary key'); + } + } + + /** + * Throws an exception when the payload has one or more unknown fields + * + * @param string $collectionName + * @param array $payload + * + * @throws BadRequestException + */ + protected function validatePayloadFields($collectionName, array $payload) + { + $collection = $this->getSchemaManager()->getCollection($collectionName); + $unknownFields = []; + + foreach (array_keys($payload) as $fieldName) { + if (!$collection->hasField($fieldName)) { + $unknownFields[] = $fieldName; + } + } + + if (!empty($unknownFields)) { + throw new BadRequestException( + sprintf('Payload fields: "%s" does not exists in "%s" collection.', implode(', ', $unknownFields), $collectionName) + ); + } + } + + /** + * Throws an exception when one or more fields in the payload fails its field validation + * + * @param string $collectionName + * @param array $payload + * + * @throws BadRequestException + */ + protected function validatePayloadWithFieldsValidation($collectionName, array $payload) + { + $collection = $this->getSchemaManager()->getCollection($collectionName); + $violations = []; + + foreach ($payload as $fieldName => $value) { + $field = $collection->getField($fieldName); + if (!$field) { + continue; + } + + if ($validation = $field->getValidation()) { + if (!is_valid_regex_pattern($validation)) { + throw new RuntimeException( + sprintf('Field "%s": "%s" is an invalid regular expression', $fieldName, $validation) + ); + } + + $violations[$fieldName] = $this->validator->getProvider()->validate($value, [ + new Regex($validation) + ]); + } + } + + $this->throwErrorIfAny($violations); + } + + /** + * @param string $collection + * @param array $payload + * @param array $params + * + * @throws ForbiddenException + */ + protected function enforcePermissions($collection, array $payload, array $params) + { + $collectionObject = $this->getSchemaManager()->getCollection($collection); + $status = null; + $statusField = $collectionObject->getStatusField(); + if ($statusField) { + $status = ArrayUtils::get($payload, $statusField->getName(), $statusField->getDefaultValue()); + } + + $acl = $this->getAcl(); + if ($acl->requireComment($collection, $status) && empty($params['comment'])) { + throw new ForbiddenException('Activity comment required for collection: ' . $collection); + } + + if ($acl->canComment($collection, $status) && !empty($params['comment'])) { + throw new ForbiddenException('You are not allowed add comment for collection: ' . $collection); + } + + // Enforce write field blacklist + $this->getAcl()->enforceWriteField($collection, array_keys($payload), $status); + } +} diff --git a/src/core/Directus/Services/ActivityService.php b/src/core/Directus/Services/ActivityService.php new file mode 100644 index 0000000000..9a37ad2c44 --- /dev/null +++ b/src/core/Directus/Services/ActivityService.php @@ -0,0 +1,152 @@ +collection = SchemaManager::COLLECTION_ACTIVITY; + $this->itemsService = new ItemsService($this->container); + } + + public function createComment(array $data, array $params = []) + { + $data = array_merge($data, [ + 'type' => DirectusActivityTableGateway::TYPE_COMMENT, + 'action' => DirectusActivityTableGateway::ACTION_ADD, + 'datetime' => DateTimeUtils::nowInUTC()->toString(), + 'ip' => get_request_ip(), + 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', + 'user' => $this->getAcl()->getUserId() + ]); + + $this->validatePayload($this->collection, null, $data, $params); + $this->enforcePermissions($this->collection, $data, $params); + + $tableGateway = $this->getTableGateway(); + + // make sure to create new one instead of update + unset($data[$tableGateway->primaryKeyFieldName]); + $newComment = $tableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $tableGateway->wrapData( + $newComment->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function updateComment($id, $comment, array $params = []) + { + $this->validate(['comment' => $comment], ['comment' => 'required']); + + $data = [ + 'id' => $id, + 'comment' => $comment, + 'datetime_edited' => DateTimeUtils::nowInUTC()->toString() + ]; + + $this->enforcePermissions($this->collection, $data, $params); + + $tableGateway = $this->getTableGateway(); + $select = new Select($this->collection); + $select->columns(['id']); + $select->where(['id' => $id, 'type' => DirectusActivityTableGateway::TYPE_COMMENT]); + if (!$tableGateway->selectWith($select)->count()) { + throw new ItemNotFoundException(); + } + + // make sure to create new one instead of update + $newComment = $tableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $tableGateway->wrapData( + $newComment->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function deleteComment($id, array $params = []) + { + $this->enforcePermissions($this->collection, [], $params); + + $tableGateway = $this->getTableGateway(); + $select = new Select($this->collection); + $select->columns(['id']); + $select->where(['id' => $id, 'type' => DirectusActivityTableGateway::TYPE_COMMENT]); + if (!$tableGateway->selectWith($select)->count()) { + throw new ItemNotFoundException(); + } + + $tableGateway->updateRecord(['id' => $id, 'deleted_comment' => true]); + } + + /** + * Finds a group by the given ID in the database + * + * @param int $id + * @param array $params + * + * @return array + */ + public function find($id, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getItemsByIdsAndSetResponseCacheTags($tableGateway, $id, $params); + } + + public function findAll(array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getItemsAndSetResponseCacheTags($tableGateway, $params); + } + + /** + * @return DirectusActivityTableGateway + */ + public function getTableGateway() + { + if (!$this->tableGateway) { + $acl = $this->container->get('acl'); + $dbConnection = $this->container->get('database'); + + $this->tableGateway = new DirectusActivityTableGateway($dbConnection, $acl); + } + + return $this->tableGateway; + } +} diff --git a/src/core/Directus/Services/AuthService.php b/src/core/Directus/Services/AuthService.php new file mode 100644 index 0000000000..55c7283a4b --- /dev/null +++ b/src/core/Directus/Services/AuthService.php @@ -0,0 +1,414 @@ +validateCredentials($email, $password); + + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + + /** @var UserInterface $user */ + $user = $auth->login([ + 'email' => $email, + 'password' => $password + ]); + + $hookEmitter = $this->container->get('hook_emitter'); + $hookEmitter->run('directus.authenticated', [$user]); + + // TODO: Move to the hook above + /** @var DirectusActivityTableGateway $activityTableGateway */ + $activityTableGateway = $this->createTableGateway('directus_activity', false); + $activityTableGateway->recordLogin($user->get('id')); + + return [ + 'data' => [ + 'token' => $this->generateAuthToken($user) + ] + ]; + } + + /** + * @param string $name + * + * @return array + */ + public function getAuthenticationRequestInfo($name) + { + return [ + 'data' => $this->getSsoAuthorizationInfo($name) + ]; + } + + /** + * Gets the basic information of a sso service + * + * @param string $name + * + * @return array + */ + public function getSsoBasicInfo($name) + { + /** @var Social $socialAuth */ + $socialAuth = $this->container->get('external_auth'); + /** @var AbstractSocialProvider $service */ + $service = $socialAuth->get($name); + $basePath = $this->container->get('path_base'); + + $iconUrl = null; + $type = $service->getConfig()->get('custom') === true ? 'custom' : 'core'; + $iconPath = sprintf('/extensions/%s/auth/%s/icon.svg', $type, $name); + if (file_exists($basePath . '/public' . $iconPath)) { + $iconUrl = get_url($iconPath); + } + + return [ + 'name' => $name, + 'icon' => $iconUrl + ]; + } + + /** + * @param string $name + * + * @return array + */ + public function getSsoAuthorizationInfo($name) + { + /** @var Social $socialAuth */ + $socialAuth = $this->container->get('external_auth'); + /** @var AbstractSocialProvider $service */ + $service = $socialAuth->get($name); + + $authorizationInfo = [ + 'authorization_url' => $service->getRequestAuthorizationUrl() + ]; + + if ($service instanceof TwoSocialProvider) { + $authorizationInfo['state'] = $service->getProvider()->getState(); + } + + return $authorizationInfo; + } + + /** + * @param string $name + * + * @return array + */ + public function getSsoCallbackInfo($name) + { + /** @var Social $socialAuth */ + $socialAuth = $this->container->get('external_auth'); + /** @var AbstractSocialProvider $service */ + $service = $socialAuth->get($name); + + return [ + 'callback_url' => $service->getRequestAuthorizationUrl() + ]; + } + + /** + * Gets the given SSO service information + * + * @param string $name + * + * @return array + */ + public function getSsoInfo($name) + { + return array_merge( + $this->getSsoBasicInfo($name), + $this->getSsoAuthorizationInfo($name), + $this->getSsoCallbackInfo($name) + ); + } + + public function handleAuthenticationRequestCallback($name, $generateRequestToken = false) + { + /** @var Social $socialAuth */ + $socialAuth = $this->container->get('external_auth'); + /** @var AbstractSocialProvider $service */ + $service = $socialAuth->get($name); + + $serviceUser = $service->handle(); + + $user = $this->authenticateWithEmail($serviceUser->getEmail()); + if ($generateRequestToken) { + $token = $this->generateRequestToken($user); + } else { + $token = $this->generateAuthToken($user); + } + + return [ + 'data' => [ + 'token' => $token + ] + ]; + } + + /** + * @param $token + * + * @return UserInterface + */ + public function authenticateWithToken($token) + { + if (JWTUtils::isJWT($token)) { + $authenticated = $this->getAuthProvider()->authenticateWithToken($token); + } else { + $authenticated = $this->getAuthProvider()->authenticateWithPrivateToken($token); + } + + return $authenticated; + } + + /** + * Authenticates a user with the given email + * + * @param $email + * + * @return \Directus\Authentication\User\User + * + * @throws UserWithEmailNotFoundException + */ + public function authenticateWithEmail($email) + { + return $this->getAuthProvider()->authenticateWithEmail($email); + } + + /** + * Authenticate an user with the SSO authorization code + * + * @param string $service + * @param array $params + * + * @return array + */ + public function authenticateWithSsoCode($service, array $params) + { + /** @var Social $socialAuth */ + $socialAuth = $this->container->get('external_auth'); + /** @var AbstractSocialProvider $service */ + $service = $socialAuth->get($service); + + if ($service instanceof OneSocialProvider) { + $data = ArrayUtils::pick($params, ['oauth_token', 'oauth_verifier']); + } else { + $data = ArrayUtils::pick($params, ['code']); + } + + $serviceUser = $service->getUserFromCode($data); + $user = $this->authenticateWithEmail($serviceUser->getEmail()); + + return [ + 'data' => [ + 'token' => $this->generateAuthToken($user) + ] + ]; + } + + /** + * Gets the access token from a sso request token + * + * @param string $token + * + * @return array + * + * @throws ExpiredRequestTokenException + * @throws InvalidRequestTokenException + */ + public function authenticateWithSsoRequestToken($token) + { + if (!JWTUtils::isJWT($token)) { + throw new InvalidRequestTokenException(); + } + + if (JWTUtils::hasExpired($token)) { + throw new ExpiredRequestTokenException(); + } + + $payload = JWTUtils::getPayload($token); + + if (!JWTUtils::hasPayloadType(JWTUtils::TYPE_SSO_REQUEST_TOKEN, $payload)) { + throw new InvalidRequestTokenException(); + } + + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + $user = $auth->getUserProvider()->findWhere([ + 'id' => $payload->id + ]); + + return [ + 'data' => [ + 'token' => $this->generateAuthToken($user) + ] + ]; + } + + /** + * Generates JWT Token + * + * @param UserInterface $user + * + * @return string + */ + public function generateAuthToken(UserInterface $user) + { + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + + return $auth->generateAuthToken($user); + } + + /** + * Generates a Request JWT Token use for SSO Authentication + * + * @param UserInterface $user + * + * @return string + */ + public function generateRequestToken(UserInterface $user) + { + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + + return $auth->generateRequestToken($user); + } + + /** + * Sends a email with the reset password token + * + * @param $email + */ + public function sendResetPasswordToken($email) + { + $this->validate(['email' => $email], ['email' => 'required|email']); + + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + $user = $auth->findUserWithEmail($email); + + $resetToken = $auth->generateResetPasswordToken($user); + + send_forgot_password_email($user->toArray(), $resetToken); + } + + public function resetPasswordWithToken($token) + { + if (!JWTUtils::isJWT($token)) { + throw new InvalidResetPasswordTokenException($token); + } + + if (JWTUtils::hasExpired($token)) { + throw new ExpiredResetPasswordToken($token); + } + + $payload = JWTUtils::getPayload($token); + + if (!JWTUtils::hasPayloadType(JWTUtils::TYPE_RESET_PASSWORD, $payload)) { + throw new InvalidResetPasswordTokenException($token); + } + + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + $userProvider = $auth->getUserProvider(); + $user = $userProvider->find($payload->id); + + if (!$user) { + throw new UserNotFoundException(); + } + + // Throw invalid token if the payload email is not the same as the current user email + if (!property_exists($payload, 'email') || $payload->email !== $user->getEmail()) { + throw new InvalidResetPasswordTokenException($token); + } + + $newPassword = StringUtils::randomString(16); + $userProvider->update($user, [ + 'password' => $auth->hashPassword($newPassword) + ]); + + send_reset_password_email($user->toArray(), $newPassword); + } + + public function refreshToken($token) + { + $this->validate([ + 'token' => $token + ], [ + 'token' => 'required' + ]); + + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + + return ['data' => ['token' => $auth->refreshToken($token)]]; + } + + /** + * @return Provider + */ + protected function getAuthProvider() + { + return $this->container->get('auth'); + } + + /** + * Validates email+password credentials + * + * @param $email + * @param $password + * + * @throws BadRequestException + */ + protected function validateCredentials($email, $password) + { + $payload = [ + 'email' => $email, + 'password' => $password + ]; + $constraints = [ + 'email' => 'required|email', + 'password' => 'required' + ]; + + // throws an exception if the constraints are not met + $this->validate($payload, $constraints); + } +} diff --git a/src/core/Directus/Services/CollectionPresetsService.php b/src/core/Directus/Services/CollectionPresetsService.php new file mode 100644 index 0000000000..02cb748110 --- /dev/null +++ b/src/core/Directus/Services/CollectionPresetsService.php @@ -0,0 +1,58 @@ +collection = SchemaManager::COLLECTION_COLLECTION_PRESETS; + $this->itemsService = new ItemsService($this->container); + } + + public function findAll(array $params = []) + { + return $this->itemsService->findAll($this->collection, $params); + } + + public function createItem(array $payload, array $params = []) + { + return $this->itemsService->createItem($this->collection, $payload, $params); + } + + public function find($id, array $params = []) + { + return $this->itemsService->find($this->collection, $id, $params); + } + + public function findByIds($id, array $params = []) + { + return $this->itemsService->find($this->collection, $id, $params); + } + + public function update($id, array $payload, array $params = []) + { + return $this->itemsService->update($this->collection, $id, $payload, $params); + } + + public function delete($id, array $params = []) + { + return $this->itemsService->delete($this->collection, $id, $params); + } +} diff --git a/src/core/Directus/Services/FilesServices.php b/src/core/Directus/Services/FilesServices.php new file mode 100644 index 0000000000..5b5dfb7830 --- /dev/null +++ b/src/core/Directus/Services/FilesServices.php @@ -0,0 +1,164 @@ +collection = SchemaManager::COLLECTION_FILES; + } + + public function create(array $data, array $params = []) + { + $this->enforcePermissions($this->collection, $data, $params); + $tableGateway = $this->createTableGateway($this->collection); + + $data['upload_user'] = $this->getAcl()->getUserId(); + $data['upload_date'] = DateTimeUtils::nowInUTC()->toString(); + + $validationConstraints = $this->createConstraintFor($this->collection); + $this->validate($data, array_merge(['data' => 'required'], $validationConstraints)); + $newFile = $tableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $tableGateway->wrapData( + append_storage_information($newFile->toArray()), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function find($id, array $params = []) + { + $tableGateway = $this->createTableGateway($this->collection); + $params['id'] = $id; + + return $this->getItemsAndSetResponseCacheTags($tableGateway , $params); + } + + public function findByIds($id, array $params = []) + { + $tableGateway = $this->createTableGateway($this->collection); + + return $this->getItemsByIdsAndSetResponseCacheTags($tableGateway , $id, $params); + } + + public function update($id, array $data, array $params = []) + { + $this->enforcePermissions($this->collection, $data, $params); + + $this->validatePayload($this->collection, array_keys($data), $data, $params); + $tableGateway = $this->createTableGateway($this->collection); + $data[$tableGateway->primaryKeyFieldName] = $id; + $newFile = $tableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $tableGateway->wrapData( + append_storage_information($newFile->toArray()), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function delete($id, array $params = []) + { + $this->enforcePermissions($this->collection, [], $params); + $tableGateway = $this->createTableGateway($this->collection); + $file = $tableGateway->getOneData($id); + + // Force delete files + // TODO: Make the hook listen to deletes and catch ALL ids (from conditions) + // and deletes every matched files + /** @var \Directus\Filesystem\Files $files */ + $files = $this->container->get('files'); + $files->delete($file); + + // Delete file record + return $tableGateway->deleteRecord($id); + } + + public function findAll(array $params = []) + { + $tableGateway = $this->createTableGateway($this->collection); + + return $this->getItemsAndSetResponseCacheTags($tableGateway, $params); + } + + public function createFolder(array $data, array $params = []) + { + $collection = 'directus_folders'; + $this->enforcePermissions($collection, $data, $params); + $this->validatePayload($collection, null, $data, $params); + + $foldersTableGateway = $this->createTableGateway($collection); + + $newFolder = $foldersTableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $foldersTableGateway->wrapData( + $newFolder->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function findFolder($id, array $params = []) + { + $foldersTableGateway = $this->createTableGateway('directus_folders'); + $params['id'] = $id; + + return $this->getItemsAndSetResponseCacheTags($foldersTableGateway, $params); + } + + public function findFolderByIds($id, array $params = []) + { + $foldersTableGateway = $this->createTableGateway('directus_folders'); + + return $this->getItemsByIdsAndSetResponseCacheTags($foldersTableGateway, $id, $params); + } + + public function updateFolder($id, array $data, array $params = []) + { + $this->enforcePermissions('directus_folders', $data, $params); + $foldersTableGateway = $this->createTableGateway('directus_folders'); + + $data['id'] = $id; + $group = $foldersTableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $foldersTableGateway->wrapData( + $group->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function findAllFolders(array $params = []) + { + $foldersTableGateway = $this->createTableGateway('directus_folders'); + + return $this->getItemsAndSetResponseCacheTags($foldersTableGateway, $params); + } + + public function deleteFolder($id, array $params = []) + { + $this->enforcePermissions('directus_folders', [], $params); + + $foldersTableGateway = $this->createTableGateway('directus_folders'); + // NOTE: check if item exists + // TODO: As noted in other places make a light function to check for it + $this->getItemsAndSetResponseCacheTags($foldersTableGateway, [ + 'id' => $id + ]); + + return $foldersTableGateway->deleteRecord($id, $this->getCRUDParams($params)); + } +} diff --git a/src/core/Directus/Services/InterfacesService.php b/src/core/Directus/Services/InterfacesService.php new file mode 100644 index 0000000000..2fa1780131 --- /dev/null +++ b/src/core/Directus/Services/InterfacesService.php @@ -0,0 +1,21 @@ +container->get('path_base'); + $this->basePath = $basePath . '/public/extensions/core/interfaces'; + } + + public function findAll(array $params = []) + { + return $this->all($this->basePath, $params); + } +} diff --git a/src/core/Directus/Services/ItemsService.php b/src/core/Directus/Services/ItemsService.php new file mode 100644 index 0000000000..4532445c92 --- /dev/null +++ b/src/core/Directus/Services/ItemsService.php @@ -0,0 +1,317 @@ +enforcePermissions($collection, $payload, $params); + $this->validatePayload($collection, null, $payload, $params); + + $tableGateway = $this->createTableGateway($collection); + + // TODO: Throw an exception if ID exist in payload + $newRecord = $tableGateway->updateRecord($payload, $this->getCRUDParams($params)); + + try { + $item = $this->find($collection, $newRecord->getId()); + } catch (\Exception $e) { + $item = null; + } + + return $item; + } + + /** + * Finds all items in a collection limited by a limit configuration + * + * @param $collection + * @param array $params + * + * @return array + */ + public function findAll($collection, array $params = []) + { + // TODO: Use repository instead of TableGateway + return $this->getItemsAndSetResponseCacheTags( + $this->createTableGateway($collection), + $params + ); + } + + /** + * Gets a single item in the given collection and id + * + * @param string $collection + * @param mixed $id + * @param array $params + * + * @return array + */ + public function find($collection, $id, array $params = []) + { + $statusValue = $this->getStatusValue($collection, $id); + $tableGateway = $this->createTableGateway($collection); + + $this->getAcl()->enforceRead($collection, $statusValue); + + return $this->getItemsByIdsAndSetResponseCacheTags($tableGateway, $id, array_merge($params, [ + 'status' => null + ])); + } + + /** + * Gets a single item in the given collection and id + * + * @param string $collection + * @param mixed $ids + * @param array $params + * + * @return array + */ + public function findByIds($collection, $ids, array $params = []) + { + $statusValue = $this->getStatusValue($collection, $ids); + $tableGateway = $this->createTableGateway($collection); + + $this->getAcl()->enforceRead($collection, $statusValue); + + return $this->getItemsByIdsAndSetResponseCacheTags($tableGateway, $ids, array_merge($params, [ + 'status' => null + ])); + } + + /** + * Gets a single item in the given collection that matches the conditions + * + * @param string $collection + * @param array $params + * + * @return array + */ + public function findOne($collection, array $params = []) + { + $tableGateway = $this->createTableGateway($collection); + + return $this->getItemsAndSetResponseCacheTags($tableGateway, array_merge($params, [ + 'single' => true + ])); + } + + /** + * Updates a single item in the given collection and id + * + * @param string $collection + * @param mixed $id + * @param array $payload + * @param array $params + * + * @return array + */ + public function update($collection, $id, $payload, array $params = []) + { + $this->enforcePermissions($collection, $payload, $params); + $this->validatePayload($collection, array_keys($payload), $payload, $params); + + $tableGateway = $this->createTableGateway($collection); + + // Fetch the entry even if it's not "published" + $params['status'] = '*'; + $payload[$tableGateway->primaryKeyFieldName] = $id; + $newRecord = $tableGateway->updateRecord($payload, $this->getCRUDParams($params)); + + try { + $item = $this->find($collection, $newRecord->getId()); + } catch (\Exception $e) { + $item = null; + } + + return $item; + } + + public function delete($collection, $id, array $params = []) + { + $this->enforcePermissions($collection, [], $params); + + // TODO: Better way to check if the item exists + // $item = $this->find($collection, $id); + + $tableGateway = $this->createTableGateway($collection); + $tableGateway->deleteRecord($id, $this->getCRUDParams($params)); + + return true; + } + + /** + * @param $collection + * @param array $items + * @param array $params + * + * @return array + * + * @throws InvalidRequestException + */ + public function batchCreate($collection, array $items, array $params = []) + { + if (!isset($items[0]) || !is_array($items[0])) { + throw new InvalidRequestException('batch create expect an array of items'); + } + + foreach ($items as $data) { + $this->validatePayload($collection, null, $data, $params); + } + + $allItems = []; + foreach ($items as $data) { + $item = $this->createItem($collection, $data, $params); + if (!is_null($item)) { + $allItems[] = $item['data']; + } + } + + if (!empty($allItems)) { + $allItems = ['data' => $allItems]; + } + + return $allItems; + } + + /** + * @param $collection + * @param array $items + * @param array $params + * + * @return array + * + * @throws InvalidRequestException + */ + public function batchUpdate($collection, array $items, array $params = []) + { + if (!isset($items[0]) || !is_array($items[0])) { + throw new InvalidRequestException('batch create expect an array of items'); + } + + foreach ($items as $data) { + $this->validatePayload($collection, array_keys($data), $data, $params); + $this->validatePayloadHasPrimaryKey($collection, $data); + } + + $collectionObject = $this->getSchemaManager()->getCollection($collection); + $allItems = []; + foreach ($items as $data) { + $id = $data[$collectionObject->getPrimaryKeyName()]; + $item = $this->update($collection, $id, $data, $params); + + if (!is_null($item)) { + $allItems[] = $item['data']; + } + } + + if (!empty($allItems)) { + $allItems = ['data' => $allItems]; + } + + return $allItems; + } + + /** + * @param $collection + * @param array $ids + * @param array $payload + * @param array $params + * + * @return array + */ + public function batchUpdateWithIds($collection, array $ids, array $payload, array $params = []) + { + $this->validatePayload($collection, array_keys($payload), $payload, $params); + + $allItems = []; + foreach ($ids as $id) { + $item = $this->update($collection, $id, $payload, $params); + if (!empty($item)) { + $allItems[] = $item['data']; + } + } + + if (!empty($allItems)) { + $allItems = ['data' => $allItems]; + } + + return $allItems; + } + + /** + * @param $collection + * @param array $ids + * @param array $params + * + * @throws ForbiddenException + */ + public function batchDeleteWithIds($collection, array $ids, array $params = []) + { + // TODO: Implement this into a hook + if ($collection === SchemaManager::COLLECTION_ROLES) { + $groupService = new RolesService($this->container); + + foreach ($ids as $id) { + $group = $groupService->find($id); + + if ($group && !$groupService->canDelete($id)) { + throw new ForbiddenException( + sprintf('You are not allowed to delete group [%s]', $group->name) + ); + } + } + } + + foreach ($ids as $id) { + $this->delete($collection, $id, $params); + } + } + + protected function getItem(BaseRowGateway $row) + { + $collection = $row->getCollection(); + $item = null; + $statusValue = $this->getStatusValue($collection, $row->getId()); + $tableGateway = $this->createTableGateway($collection); + + if ($this->getAcl()->canRead($collection, $statusValue)) { + $params['id'] = $row->getId(); + $params['status'] = null; + $item = $this->getItemsAndSetResponseCacheTags($tableGateway, $params); + } + + return $item; + } + + protected function getStatusValue($collection, $id) + { + $collectionObject = $this->getSchemaManager()->getCollection($collection); + + if (!$collectionObject->hasStatusField()) { + return null; + } + + $primaryFieldName = $collectionObject->getPrimaryKeyName(); + $tableGateway = new TableGateway($collection, $this->getConnection()); + $select = $tableGateway->getSql()->select(); + $select->columns([$collectionObject->getStatusField()->getName()]); + $select->where([ + $primaryFieldName => $id + ]); + + $row = $tableGateway->selectWith($select)->current(); + + return $row[$collectionObject->getStatusField()->getName()]; + } +} diff --git a/src/core/Directus/Services/ListingsService.php b/src/core/Directus/Services/ListingsService.php new file mode 100644 index 0000000000..a686bf3630 --- /dev/null +++ b/src/core/Directus/Services/ListingsService.php @@ -0,0 +1,21 @@ +container->get('path_base'); + $this->basePath = $basePath . '/public/extensions/core/listings'; + } + + public function findAll(array $params = []) + { + return $this->all($this->basePath, $params); + } +} diff --git a/src/core/Directus/Services/PagesService.php b/src/core/Directus/Services/PagesService.php new file mode 100644 index 0000000000..0f55a5c7ba --- /dev/null +++ b/src/core/Directus/Services/PagesService.php @@ -0,0 +1,21 @@ +container->get('path_base'); + $this->basePath = $basePath . '/public/extensions/core/pages'; + } + + public function findAll(array $params = []) + { + return $this->all($this->basePath, $params); + } +} diff --git a/src/core/Directus/Services/PermissionsService.php b/src/core/Directus/Services/PermissionsService.php new file mode 100644 index 0000000000..18d47a513d --- /dev/null +++ b/src/core/Directus/Services/PermissionsService.php @@ -0,0 +1,98 @@ +collection = SchemaManager::COLLECTION_PERMISSIONS; + } + + /** + * @param array $data + * @param array $params + * + * @return array + */ + public function create(array $data, array $params = []) + { + $this->enforcePermissions($this->collection, $data, $params); + $this->validatePayload($this->collection, null, $data, $params); + + $tableGateway = $this->getTableGateway(); + $newGroup = $tableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $tableGateway->wrapData( + $newGroup->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function find($id, array $params = []) + { + $params['id'] = $id; + + return $this->getItemsAndSetResponseCacheTags($this->getTableGateway(), $params); + } + + public function findByIds($id, array $params = []) + { + return $this->getItemsByIdsAndSetResponseCacheTags($this->getTableGateway(), $id, $params); + } + + public function update($id, array $data, array $params = []) + { + $this->enforcePermissions($this->collection, $data, $params); + $this->validatePayload($this->collection, array_keys($data), $data, $params); + + $tableGateway = $this->getTableGateway(); + $data['id'] = $id; + $newGroup = $tableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $tableGateway->wrapData( + $newGroup->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function delete($id, array $params = []) + { + $this->enforcePermissions($this->collection, [], $params); + $this->validate(['id' => $id], $this->createConstraintFor($this->collection, ['id'])); + $tableGateway = $this->getTableGateway(); + $this->getItemsAndSetResponseCacheTags($tableGateway, [ + 'id' => $id + ]); + + $tableGateway->deleteRecord($id, $this->getCRUDParams($params)); + + return true; + } + + public function findAll(array $params = []) + { + return $this->getItemsAndSetResponseCacheTags($this->getTableGateway(), $params); + } + + /** + * @return \Directus\Database\TableGateway\RelationalTableGateway + */ + protected function getTableGateway() + { + return $this->createTableGateway($this->collection); + } +} diff --git a/src/core/Directus/Services/RelationsService.php b/src/core/Directus/Services/RelationsService.php new file mode 100644 index 0000000000..37564ea73f --- /dev/null +++ b/src/core/Directus/Services/RelationsService.php @@ -0,0 +1,72 @@ +collection = SchemaManager::COLLECTION_RELATIONS; + $this->itemsService = new ItemsService($this->container); + } + + public function create(array $data, array $params = []) + { + return $this->itemsService->createItem($this->collection, $data, $params); + } + + public function find($id, array $params = []) + { + return $this->itemsService->find($this->collection, $id, $params); + } + + public function findByIds($id, array $params = []) + { + return $this->itemsService->findByIds($this->collection, $id, $params); + } + + public function update($id, array $data, array $params = []) + { + return $this->itemsService->update($this->collection, $id, $data, $params); + } + + public function delete($id, array $params = []) + { + return $this->itemsService->delete($this->collection, $id, $params); + } + + public function findAll(array $params = []) + { + return $this->itemsService->findAll($this->collection, $params); + } + + public function batchCreate(array $payload, array $params = []) + { + return $this->itemsService->batchCreate($this->collection, $payload, $params); + } + + public function batchUpdateWithIds(array $ids, array $payload, array $params = []) + { + return $this->itemsService->batchUpdateWithIds($this->collection, $ids, $payload, $params); + } + + public function batchDeleteWithIds(array $ids, array $params = []) + { + return $this->itemsService->batchDeleteWithIds($this->collection, $ids, $params); + } +} diff --git a/src/core/Directus/Services/RevisionsService.php b/src/core/Directus/Services/RevisionsService.php new file mode 100644 index 0000000000..0c85c61d44 --- /dev/null +++ b/src/core/Directus/Services/RevisionsService.php @@ -0,0 +1,193 @@ +collection = SchemaManager::COLLECTION_REVISIONS; + } + + /** + * Returns all items from revisions + * + * Result count will be limited by the rows per page setting + * + * @param array $params + * + * @return array|mixed + */ + public function findAll(array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getDataAndSetResponseCacheTags( + [$tableGateway, 'getItems'], + [$params] + ); + } + + /** + * Returns all revisions for a specific collection + * + * Result count will be limited by the rows per page setting + * + * @param $collection + * @param array $params + * + * @return array|mixed + */ + public function findByCollection($collection, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getDataAndSetResponseCacheTags( + [$tableGateway, 'getItems'], + [array_merge_recursive($params, ['filter' => ['collection' => $collection]])] + ); + } + + /** + * Returns one revision with the given id + * + * @param int $id + * @param array $params + * + * @return array + */ + public function findOne($id, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getDataAndSetResponseCacheTags( + [$tableGateway, 'getItems'], + [array_merge_recursive($params, ['filter' => ['id' => $id]])] + ); + } + + /** + * Returns one or more revision with the given ids + * + * @param int $id + * @param array $params + * + * @return array + */ + public function findByIds($id, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getItemsByIdsAndSetResponseCacheTags($tableGateway, $id, $params); + } + + /** + * Returns all revision from a given item + * + * @param string $collection + * @param mixed $item + * @param array $params + * + * @return array + */ + public function findAllByItem($collection, $item, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getDataAndSetResponseCacheTags( + [$tableGateway, 'getItems'], + [array_merge($params, ['filter' => ['item' => $item, 'collection' => $collection]])] + ); + } + + /** + * @param string $collection + * @param string $item + * @param int $offset + * @param array $params + * + * @return array|mixed + */ + public function findOneByItemOffset($collection, $item, $offset, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + $this->validate(['offset' => $offset], ['offset' => 'required|numeric']); + + return $this->getDataAndSetResponseCacheTags( + [$tableGateway, 'getItems'], + [array_merge( + $params, + [ + // Make sure it's sorted by ID ascending + // to proper pick the offset based on creation order + 'sort' => 'id', + 'filter' => ['item' => $item, 'collection' => $collection], + 'single' => true, + 'limit' => 1, + 'offset' => (int)$offset + ] + )] + ); + } + + public function revert($collectionName, $item, $revision, array $params = []) + { + $this->throwErrorIfSystemTable($collectionName); + $revisionTableGateway = new TableGateway(SchemaManager::COLLECTION_REVISIONS, $this->getConnection()); + $select = $revisionTableGateway->getSql()->select(); + $select->columns(['delta']); + $select->where->equalTo('id', $revision); + $select->where->equalTo('collection', $collectionName); + $select->where->equalTo('item', $item); + + $result = $revisionTableGateway->selectWith($select)->current(); + if (!$result) { + throw new RevisionNotFoundException($revision); + } + + $data = json_decode($result->delta, true); + if (!$data) { + throw new RevisionInvalidDeltaException($revision); + } + + $collection = SchemaService::getCollection($collectionName); + $tableGateway = $this->createTableGateway($collectionName); + + $data[$collection->getPrimaryKeyName()] = $item; + $tableGateway->revertRecord($data); + + return $this->getDataAndSetResponseCacheTags( + [$tableGateway, 'getItems'], + [array_merge( + $params, + [ + 'single' => true, + 'id' => $item + ] + )] + ); + } + + /** + * @return \Directus\Database\TableGateway\RelationalTableGateway + */ + protected function getTableGateway() + { + return $this->createTableGateway($this->collection); + } +} diff --git a/src/core/Directus/Services/RolesService.php b/src/core/Directus/Services/RolesService.php new file mode 100644 index 0000000000..78750c157b --- /dev/null +++ b/src/core/Directus/Services/RolesService.php @@ -0,0 +1,180 @@ +collection = SchemaManager::COLLECTION_ROLES; + $this->itemsService = new ItemsService($this->container); + } + + public function create(array $data, array $params = []) + { + $this->validatePayload($this->collection, null, $data, $params); + $this->enforcePermissions($this->collection, $data, $params); + + $groupsTableGateway = $this->createTableGateway($this->collection); + // make sure to create new one instead of update + unset($data[$groupsTableGateway->primaryKeyFieldName]); + $newGroup = $groupsTableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $groupsTableGateway->wrapData( + $newGroup->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + /** + * Finds a group by the given ID in the database + * + * @param int $id + * @param array $params + * + * @return array + */ + public function find($id, array $params = []) + { + $tableGateway = $this->getTableGateway(); + $params['id'] = $id; + + return $this->getItemsAndSetResponseCacheTags($tableGateway, $params); + } + + /** + * Finds one or more roles by the given IDs + * + * @param int $id + * @param array $params + * + * @return array + */ + public function findByIds($id, array $params = []) + { + $tableGateway = $this->getTableGateway(); + + return $this->getItemsByIdsAndSetResponseCacheTags($tableGateway, $id, $params); + } + + /** + * Gets a single item that matches the conditions + * + * @param array $params + * + * @return array + */ + public function findOne(array $params = []) + { + return $this->itemsService->findOne($this->collection, $params); + } + + public function update($id, array $data, array $params = []) + { + $this->validatePayload($this->collection, array_keys($data), $data, $params); + $this->enforcePermissions($this->collection, $data, $params); + + $groupsTableGateway = $this->getTableGateway(); + + $data['id'] = $id; + $group = $groupsTableGateway->updateRecord($data, $this->getCRUDParams($params)); + + return $groupsTableGateway->wrapData( + $group->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function findAll(array $params = []) + { + $groupsTableGateway = $this->getTableGateway(); + + return $this->getItemsAndSetResponseCacheTags($groupsTableGateway, $params); + } + + public function delete($id, array $params = []) + { + $this->enforcePermissions($this->collection, [], $params); + $this->validate(['id' => $id], $this->createConstraintFor($this->collection, ['id'])); + + // TODO: Create exists method + // NOTE: throw an exception if item does not exists + $group = $this->find($id); + + // TODO: Make the error messages more specific + if (!$this->canDelete($id)) { + throw new UnauthorizedException(sprintf('You are not allowed to delete group [%s]', $id)); + } + + $tableGateway = $this->getTableGateway(); + + $tableGateway->deleteRecord($id, $this->getCRUDParams($params)); + + return true; + } + + /** + * Checks whether the the group be deleted + * + * @param $id + * @param bool $fetchNew + * + * @return bool + */ + public function canDelete($id, $fetchNew = false) + { + if (!$this->lastGroup || $fetchNew === true) { + $group = $this->find($id); + } else { + $group = $this->lastGroup; + } + + // TODO: RowGateWay should parse values against their column type + return !(!$group || $group->id == 1 || strtolower($group->name) === 'public'); + } + + /** + * @return DirectusRolesTableGateway + */ + public function getTableGateway() + { + if (!$this->tableGateway) { + $acl = $this->container->get('acl'); + $dbConnection = $this->container->get('database'); + + $this->tableGateway = new DirectusRolesTableGateway($dbConnection, $acl); + } + + return $this->tableGateway; + } +} diff --git a/src/core/Directus/Services/ScimService.php b/src/core/Directus/Services/ScimService.php new file mode 100644 index 0000000000..e1870f4351 --- /dev/null +++ b/src/core/Directus/Services/ScimService.php @@ -0,0 +1,663 @@ +usersService = new UsersService($this->container); + $this->rolesService = new RolesService($this->container); + } + + public function createUser(array $data, array $params = []) + { + // TODO: Validate the payload schema + $user = $this->usersService->create($this->parseScimUserData($data), $params); + + if ($user) { + $user = $this->parseUserData(ArrayUtils::get($user, 'data', [])); + } + + return $user; + } + + public function createGroup(array $data, array $params = []) + { + // TODO: Validate the payload schema + $user = $this->rolesService->create($this->parseScimGroupData($data), $params); + + if ($user) { + $user = $this->parseGroupData(ArrayUtils::get($user, 'data', [])); + } + + return $user; + } + + public function updateUser($id, array $data, array $params = []) + { + $user = $this->usersService->findOne([ + 'fields' => 'id', + 'single' => true, + 'filter' => [ + 'external_id' => $id + ] + ]); + + $parsedData = $this->parseScimUserData($data); + ArrayUtils::pull($parsedData, 'external_id'); + + $user = $this->usersService->update(ArrayUtils::get($user, 'data.id'), $parsedData, $params); + + if ($user) { + $user = $this->parseUserData(ArrayUtils::get($user, 'data', [])); + } + + return $user; + } + + public function updateGroup($id, array $data, array $params = []) + { + $user = $this->rolesService->findOne([ + 'fields' => 'id', + 'single' => true, + 'filter' => [ + 'external_id' => $id + ] + ]); + + $parsedData = $this->parseScimGroupData($data); + ArrayUtils::pull($parsedData, 'external_id'); + + $user = $this->rolesService->update(ArrayUtils::get($user, 'data.id'), $parsedData, $params); + + if ($user) { + $user = $this->parseGroupData(ArrayUtils::get($user, 'data', [])); + } + + return $user; + } + + /** + * Returns the data of the give user id + * + * @param mixed $id + * @param array $params + * @return array + * + * @throws BadRequestException + */ + public function findUser($id, array $params = []) + { + if (empty($id)) { + throw new BadRequestException('id cannot be empty'); + } + + $userData = $this->usersService->findOne( + [ + 'single' => true, + 'filter' => ['external_id' => $id] + ] + ); + + return $this->parseUserData(ArrayUtils::get($userData, 'data', [])); + } + + /** + * Returns the data of the give group id + * + * @param mixed $id + * @param array $params + * @return array + * + * @throws BadRequestException + */ + public function findGroup($id, array $params = []) + { + if (empty($id)) { + throw new BadRequestException('id cannot be empty'); + } + + $roleData = $this->rolesService->findOne( + [ + 'single' => true, + 'filter' => ['external_id' => $id], + 'fields' => [ + '*', + 'users.*.*' + ] + ] + ); + + return $this->parseGroupData(ArrayUtils::get($roleData, 'data', [])); + } + + /** + * Returns a list of users + * + * @param array $scimParams + * + * @return array + */ + public function findAllUsers(array $scimParams = []) + { + $parameters = $this->parseListParameters(static::RESOURCE_USER, $scimParams); + $items = $this->usersService->findAll($parameters); + + return $this->parseUsersData( + ArrayUtils::get($items, 'data', []), + ArrayUtils::get($items, 'meta', []), + $parameters + ); + } + + /** + * Returns a list of groups + * + * @param array $scimParams + * + * @return array + */ + public function findAllGroups(array $scimParams = []) + { + $parameters = $this->parseListParameters(static::RESOURCE_GROUP, $scimParams); + $items = $this->rolesService->findAll(array_merge($parameters, [ + 'fields' => [ + '*', + 'users.*.*' + ] + ])); + + return $this->parseGroupsData( + ArrayUtils::get($items, 'data', []), + ArrayUtils::get($items, 'meta', []), + $parameters + ); + } + + /** + * @param mixed $id + * @param array $params + * + * @return bool + */ + public function deleteGroup($id, array $params = []) + { + $role = $this->rolesService->findOne([ + 'fields' => 'id', + 'single' => true, + 'filter' => [ + 'external_id' => $id + ] + ]); + + return $this->rolesService->delete(ArrayUtils::get($role, 'data.id')); + } + + /** + * Parse Scim parameters into Directus parameters + * + * @param string $resourceType + * @param array $scimParams + * + * @return array + */ + protected function parseListParameters($resourceType, array $scimParams) + { + $filter = $this->getFilter($resourceType, ArrayUtils::get($scimParams, 'filter')); + + $parameters = [ + 'filter' => $filter + ]; + + if (ArrayUtils::has($scimParams, 'startIndex')) { + $offset = (int)ArrayUtils::get($scimParams, 'startIndex', 1); + $parameters['offset'] = $offset - 1; + } + + if (ArrayUtils::has($scimParams, 'count')) { + $limit = (int)ArrayUtils::get($scimParams, 'count', 0); + $parameters['limit'] = $limit > 0 ? $limit : 0; + } + + $parameters['meta'] = '*'; + + return $parameters; + } + + /** + * @param string $filter + * + * @param string $resourceType + * + * @return array + * + * @throws BadRequestException + */ + protected function getFilter($resourceType, $filter) + { + if (empty($filter)) { + return []; + } + + if (!is_string($filter)) { + throw new BadRequestException('Filter must be a string'); + } + + $filterParts = preg_split('/\s+/', $filter); + + if (count($filterParts) !== 3) { + throw new BadRequestException('Filter must be: '); + } + + $attribute = $filterParts[0]; + $operator = $filterParts[1]; + $value = trim($filterParts[2], '"'); + if (!$this->isOperatorSupported($operator)) { + throw new BadRequestException( + sprintf('Unsupported operator "%s"', $operator) + ); + } + + return [ + $this->convertFilterAttribute($resourceType, $attribute) => [$operator => $value] + ]; + } + + /** + * Converts scim user attribute to directus's user attribute + * + * @param string $resourceType + * @param string $attribute + * + * @return string + * + * @throws BadRequestException + */ + public function convertFilterAttribute($resourceType, $attribute) + { + $resourcesMapping = $this->getFilterAttributesMapping(); + $mapping = ArrayUtils::get($resourcesMapping, $resourceType, []); + + if (!array_key_exists($attribute, $mapping)) { + throw new BadRequestException( + sprintf('Unknown attribute "%s"', $attribute) + ); + } + + return $mapping[$attribute]; + } + + /** + * Parse Scim user data + * + * @param array $data + * + * @return array + */ + public function parseScimUserData(array $data) + { + $userData = []; + + if (ArrayUtils::has($data, 'userName')) { + $userData['email'] = $data['userName']; + } + + if (ArrayUtils::has($data, 'externalId')) { + $userData['external_id'] = $data['externalId']; + } + + if (ArrayUtils::has($data, 'name')) { + if (is_array($data['name']) && !empty($data['name'])) { + $name = $data['name']; + $firstName = ArrayUtils::get($name, 'givenName'); + $lastName = ArrayUtils::get($name, 'familyName'); + } else { + $firstName = $lastName = null; + } + + $userData['first_name'] = $firstName; + $userData['last_name'] = $lastName; + } + + if ( + ArrayUtils::has($data, 'emails') + && is_array($data['emails']) + && ArrayUtils::isNumericKeys($data['emails']) + ) { + $email = null; + foreach ($data['emails'] as $emailData) { + $email = ArrayUtils::get($emailData, 'value'); + if (isset($emailData['primary']) && $emailData['primary'] === true) { + break; + } + } + + if ($email) { + $userData['email'] = $email; + } + } + + if (ArrayUtils::has($data, 'locale')) { + $userData['locale'] = $data['locale']; + } + + if (ArrayUtils::has($data, 'timezone')) { + $userData['timezone'] = $data['timezone']; + } + + if (ArrayUtils::has($data, 'active')) { + $userData['status'] = $data['active'] === true + ? DirectusUsersTableGateway::STATUS_ACTIVE + : DirectusUsersTableGateway::STATUS_DISABLED; + } + + return $userData; + } + + /** + * Parse Scim group data + * + * @param array $data + * + * @return array + */ + public function parseScimGroupData(array $data) + { + $groupData = []; + + if (ArrayUtils::has($data, 'displayName')) { + $groupData['name'] = $data['displayName']; + } + + if (ArrayUtils::has($data, 'id')) { + $groupData['external_id'] = $data['id']; + } + + return $groupData; + } + + /** + * Attributes mapping from scim schema to directus users schema + * + * @return array + */ + public function getFilterAttributesMapping() + { + return [ + static::RESOURCE_GROUP => [ + 'displayName' => 'name', + ], + + static::RESOURCE_USER => [ + 'userName' => 'email', + 'externalId' => 'external_id', + 'id' => 'id', + 'emails.value' => 'email' + ] + ]; + } + + /** + * Checks whether the given operator is supported + * + * @param string $operator + * + * @return bool + */ + public function isOperatorSupported($operator) + { + return in_array(strtolower($operator), $this->getOperators()); + } + + /** + * List of supported operators + * + * @return array + */ + public function getOperators() + { + return [ + 'eq' + ]; + } + + /** + * Parses a list of users data into a SCIM schema + * + * @param array $list + * @param array $meta + * @param array $parameters + * + * @return array + */ + public function parseUsersData(array $list, array $meta = [], array $parameters = []) + { + $items = []; + foreach ($list as $item) { + $items[] = $this->parseUserData($item); + } + + return $this->addSchemaListAttributes($items, $meta, $parameters); + } + + /** + * Parses a list of users data into a SCIM schema + * + * @param array $list + * @param array $meta + * @param array $parameters + * + * @return array + */ + public function parseGroupsData(array $list, array $meta = [], array $parameters = []) + { + $items = []; + foreach ($list as $item) { + $items[] = $this->parseGroupData($item); + } + + return $this->addSchemaListAttributes($items, $meta, $parameters); + } + + /** + * Parses an user data into a SCIM schema + * + * @param array $data + * + * @return array + */ + public function parseUserData(array $data) + { + // TODO: This is a real example on how we need to improve the response filter + $tableGateway = $this->usersService->getTableGateway(); + + $userId = ArrayUtils::get($data, $tableGateway->primaryKeyFieldName); + $externalId = ArrayUtils::get($data, 'external_id'); + $email = ArrayUtils::get($data, 'email'); + $firstName = ArrayUtils::get($data, 'first_name'); + $lastName = ArrayUtils::get($data, 'last_name'); + $location = get_url($this->container->get('router')->pathFor('scim_v2_read_user', [ + 'env' => get_api_env_from_request(), + 'id' => $externalId + ])); + + return [ + 'schemas' => [static::SCHEMA_USER], + 'id' => $externalId, + 'externalId' => $userId, + 'meta' => [ + 'resourceType' => 'User', + // 'created' => null, + // 'lastModified' => null, + 'location' => $location, + 'version' => sprintf('W/"%s"', md5($lastName . $firstName . $email)) + ], + 'name' => [ + // 'formatted' => sprintf('%s %s', $firstName, $lastName), + 'familyName' => $lastName, + 'givenName' => $firstName + ], + 'userName' => $email, + // 'phoneNumbers' => [], + 'emails' => [ + [ + 'value' => $email, + 'type' => 'work', + 'primary' => true + ] + ], + 'locale' => ArrayUtils::get($data, 'locale'), + 'timezone' => ArrayUtils::get($data, 'timezone'), + 'active' => ArrayUtils::get($data, 'status') === DirectusUsersTableGateway::STATUS_ACTIVE + ]; + } + + /** + * Parses an group data into a SCIM schema + * + * @param array $data + * + * @return array + */ + public function parseGroupData(array $data) + { + // TODO: This is a real example on how we need to improve the response filter + $tableGateway = $this->usersService->getTableGateway(); + + $userId = ArrayUtils::get($data, $tableGateway->primaryKeyFieldName); + $externalId = ArrayUtils::get($data, 'external_id'); + // $email = ArrayUtils::get($data, 'email'); + // $firstName = ArrayUtils::get($data, 'first_name'); + // $lastName = ArrayUtils::get($data, 'last_name'); + $location = get_url($this->container->get('router')->pathFor('scim_v2_read_group', [ + 'env' => get_api_env_from_request(), + 'id' => $externalId + ])); + + $eTag = sprintf( + 'W/"%s"', + md5( + ArrayUtils::get($data, 'ip_whitelist') + . ArrayUtils::get($data, 'name') + . ArrayUtils::get($data, 'nav_blacklist') + ) + ); + + $users = ArrayUtils::get($data, 'users', []); + $members = array_map(function ($junction) { + $user = $junction['user']; + return [ + 'value' => ArrayUtils::get($user, 'email'), + '$ref' => get_url($this->container->get('router')->pathFor('scim_v2_read_user', [ + 'env' => get_api_env_from_request(), + 'id' => ArrayUtils::get($user, 'id') + ])), + 'display' => sprintf( + '%s %s', + ArrayUtils::get($user, 'first_name'), ArrayUtils::get($user, 'last_name') + ) + ]; + }, $users); + + return [ + 'schemas' => [static::SCHEMA_GROUP], + 'id' => $externalId, + 'externalId' => $userId, + 'meta' => [ + 'resourceType' => 'Group', + // 'created' => null, + // 'lastModified' => null, + 'location' => $location, + 'version' => $eTag + ], + 'displayName' => ArrayUtils::get($data, 'name'), + 'members' => $members + ]; + } + + /** + * Adds Schema List attributes such as totalResults + * + * @param array $items + * @param array $meta + * @param array $parameters + * + * @return array + */ + protected function addSchemaListAttributes(array $items, array $meta, array $parameters) + { + $result = [ + 'schemas' => [static::SCHEMA_LIST] + ]; + + if (ArrayUtils::has($parameters, 'offset')) { + $startIndex = ArrayUtils::get($parameters, 'offset'); + $result['startIndex'] = $startIndex + 1; + } + + if (ArrayUtils::has($parameters, 'limit')) { + $totalResults = ArrayUtils::get($meta, 'total_count'); + $result['itemsPerPage'] = count($items); + } else { + $totalResults = count($items); + } + + $result['totalResults'] = $totalResults; + + $result['Resources'] = $items; + + return $result; + } + + /** + * @param array $params + * + * @return array + */ + protected function getFieldsParams(array $params) + { + $attributes = ArrayUtils::createFromCSV(ArrayUtils::get($params, 'attributes')); + $excludedAttributes = ArrayUtils::createFromCSV(ArrayUtils::get($params, 'excludedAttributes')); + + if (!empty($excludedAttributes)) { + $excludedAttributes = array_map(function ($attribute) { + return '-' . $attribute; + }, $excludedAttributes); + } + + $fields = array_merge($attributes, $excludedAttributes); + + $result = []; + if ($fields) { + $result['fields'] = $fields; + } + + return $result; + } +} diff --git a/src/core/Directus/Services/ServerService.php b/src/core/Directus/Services/ServerService.php new file mode 100644 index 0000000000..173b818780 --- /dev/null +++ b/src/core/Directus/Services/ServerService.php @@ -0,0 +1,32 @@ +getAcl()->isAdmin()) { + throw new UnauthorizedException('Only Admin can see this information'); + } + + return [ + 'data' => [ + 'api' => [ + 'version' => Application::DIRECTUS_VERSION + ], + 'server' => [ + 'general' => [ + 'php_version' => phpversion(), + 'php_api' => php_sapi_name() + ], + 'max_upload_size' => get_max_upload_size() + ] + ] + ]; + } +} diff --git a/src/core/Directus/Services/SettingsService.php b/src/core/Directus/Services/SettingsService.php new file mode 100644 index 0000000000..9df0d4cff2 --- /dev/null +++ b/src/core/Directus/Services/SettingsService.php @@ -0,0 +1,57 @@ +collection = SchemaManager::COLLECTION_SETTINGS; + $this->itemsService = new ItemsService($this->container); + } + + public function create(array $data, array $params = []) + { + return $this->itemsService->createItem($this->collection, $data, $params); + } + + public function find($id, array $params = []) + { + return $this->itemsService->find($this->collection, $id, $params); + } + + public function findByIds($id, array $params = []) + { + return $this->itemsService->findByIds($this->collection, $id, $params); + } + + public function update($id, array $data, array $params = []) + { + return $this->itemsService->update($this->collection, $id, $data, $params); + } + + public function delete($id, array $params = []) + { + return $this->itemsService->delete($this->collection, $id, $params); + } + + public function findAll(array $params = []) + { + return $this->itemsService->findAll($this->collection, $params); + } +} diff --git a/src/core/Directus/Services/TablesService.php b/src/core/Directus/Services/TablesService.php new file mode 100644 index 0000000000..14035c1037 --- /dev/null +++ b/src/core/Directus/Services/TablesService.php @@ -0,0 +1,1285 @@ +collection = SchemaManager::COLLECTION_COLLECTIONS; + } + + public function findAll(array $params = []) + { + $tableGateway = $this->createTableGateway($this->collection); + + $result = $tableGateway->getItems($params); + + $result['data'] = $this->mergeSchemaCollections($result['data']); + + return $result; + } + + public function findAllFields($collectionName, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + // $this->tagResponseCache('tableColumnsSchema_'.$tableName); + + $this->validate(['collection' => $collectionName], ['collection' => 'required|string']); + + /** @var SchemaManager $schemaManager */ + $schemaManager = $this->container->get('schema_manager'); + $collection = $schemaManager->getCollection($collectionName); + if (!$collection) { + throw new CollectionNotFoundException($collectionName); + } + + $tableGateway = $this->getFieldsTableGateway(); + $result = $tableGateway->getItems(array_merge($params, [ + 'filter' => [ + 'collection' => $collectionName + ] + ])); + + $result['data'] = $this->mergeMissingSchemaFields($collection, $result['data']); + + return $result; + } + + public function find($name, array $params = []) + { + $this->validate(['collection' => $name], ['collection' => 'required|string']); + + $tableGateway = $this->createTableGateway($this->collection); + + return $tableGateway->getItems(array_merge($params, [ + 'id' => $name + ])); + } + + public function findByIds($name, array $params = []) + { + $this->validate(['collection' => $name], ['collection' => 'required|string']); + + $tableGateway = $this->createTableGateway($this->collection); + + try { + $result = $tableGateway->getItemsByIds($name, $params); + $collectionNames = StringUtils::csv((string) $name); + $result['data'] = $this->mergeMissingSchemaCollections($collectionNames, $result['data']); + } catch (ItemNotFoundException $e) { + $data = $this->mergeSchemaCollection($name, []); + + $result = $tableGateway->wrapData($data, true, ArrayUtils::get($params, 'meta')); + } + + return $result; + } + + public function findField($collection, $field, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + $this->validate([ + 'collection' => $collection, + 'field' => $field + ], [ + 'collection' => 'required|string', + 'field' => 'required|string' + ]); + + /** @var SchemaManager $schemaManager */ + $schemaManager = $this->container->get('schema_manager'); + $collectionObject = $schemaManager->getCollection($collection); + if (!$collectionObject) { + throw new CollectionNotFoundException($collection); + } + + $columnObject = $collectionObject->getField($field); + if (!$columnObject) { + throw new FieldNotFoundException($field); + } + + $tableGateway = $this->getFieldsTableGateway(); + + if ($columnObject->isManaged()) { + $params = ArrayUtils::pick($params, ['meta', 'fields']); + $params['single'] = true; + $params['filter'] = [ + 'collection' => $collection, + 'field' => $field + ]; + + $result = $tableGateway->getItems($params); + $fieldData = $this->mergeMissingSchemaField($collectionObject, $result['data']); + if ($fieldData) { + $result['data'] = $fieldData; + } + } else { + // Get not managed fields + $result = $tableGateway->wrapData( + $this->mergeSchemaField($columnObject), + true, + ArrayUtils::pick($params, 'meta') + ); + } + + return $result; + } + + public function findFields($collectionName, array $fieldsName, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + // $this->tagResponseCache('tableColumnsSchema_'.$tableName); + $this->validate(['fields' => $fieldsName], ['fields' => 'required|array']); + + /** @var SchemaManager $schemaManager */ + $schemaManager = $this->container->get('schema_manager'); + $collection = $schemaManager->getCollection($collectionName); + if (!$collection) { + throw new CollectionNotFoundException($collectionName); + } + + $tableGateway = $this->getFieldsTableGateway(); + $result = $tableGateway->getItems(array_merge($params, [ + 'filter' => [ + 'collection' => $collectionName, + 'field' => ['in' => $fieldsName] + ] + ])); + + $result['data'] = $this->mergeMissingSchemaFields($collection, ArrayUtils::get($result, 'data'), $fieldsName); + + return $result; + } + + public function deleteField($collection, $field, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + $this->enforcePermissions('directus_fields', [], $params); + + $this->validate([ + 'collection' => $collection, + 'field' => $field + ], [ + 'collection' => 'required|string', + 'field' => 'required|string' + ]); + + $tableService = new TablesService($this->container); + + $tableService->dropColumn($collection, $field); + + return true; + } + + /** + * + * @param string $name + * @param array $data + * @param array $params + * + * @return array + * + * @throws ErrorException + * @throws InvalidRequestException + * @throws CollectionAlreadyExistsException + * @throws UnauthorizedException + * @throws BadRequestException + */ + public function createTable($name, array $data = [], array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Authorized to create collections'); + } + + $this->enforcePermissions($this->collection, $data, $params); + + $data['collection'] = $name; + $collectionsCollectionName = 'directus_collections'; + $collectionsCollectionObject = $this->getSchemaManager()->getCollection($collectionsCollectionName); + $constraints = $this->createConstraintFor($collectionsCollectionName, $collectionsCollectionObject->getFieldsName()); + + $this->validate($data, array_merge(['fields' => 'array'], $constraints)); + + if (!$this->isValidName($name)) { + throw new InvalidRequestException('Invalid collection name'); + } + + $collection = null; + + try { + $collection = $this->getSchemaManager()->getCollection($name); + } catch (CollectionNotFoundException $e) { + // TODO: Default to primary key id + $constraints['fields'][] = 'required'; + + $this->validate($data, array_merge(['fields' => 'array'], $constraints)); + } + + // ---------------------------------------------------------------------------- + + if ($collection && $collection->isManaged()) { + throw new CollectionAlreadyExistsException($name); + } + + if (!$this->hasPrimaryField($data['fields'])) { + throw new BadRequestException('Collection does not have a primary key field.'); + } + + if (!$this->hasUniquePrimaryField($data['fields'])) { + throw new BadRequestException('Collection must only have one primary key field.'); + } + + if (!$this->hasUniqueAutoIncrementField($data['fields'])) { + throw new BadRequestException('Collection must only have one auto increment field.'); + } + + if (!$this->hasUniqueFieldsName($data['fields'])) { + throw new BadRequestException('Collection fields name must be unique.'); + } + + if ($collection && !$collection->isManaged()) { + $success = $this->updateTableSchema($collection, $data); + } else { + $success = $this->createTableSchema($name, $data); + } + + if (!$success) { + throw new ErrorException('Error creating the collection'); + } + + $collectionsTableGateway = $this->createTableGateway('directus_collections'); + + $fields = ArrayUtils::get($data, 'fields'); + if ($collection && !$collection->isManaged() && !$fields) { + $fields = $collection->getFieldsArray(); + } + + $this->addColumnsInfo($name, $fields); + + $item = ArrayUtils::omit($data, 'fields'); + $item['collection'] = $name; + + $table = $collectionsTableGateway->updateRecord($item); + + // ---------------------------------------------------------------------------- + + $collectionTableGateway = $this->createTableGateway($collectionsCollectionName); + $tableData = $collectionTableGateway->parseRecord($table->toArray()); + + return $collectionTableGateway->wrapData($tableData, true, ArrayUtils::get($params, 'meta')); + } + + /** + * Updates a table + * + * @param $name + * @param array $data + * @param array $params + * + * @return array + * + * @throws CollectionNotManagedException + * @throws ErrorException + * @throws CollectionNotFoundException + * @throws UnauthorizedException + */ + public function updateTable($name, array $data, array $params = []) + { + // TODO: Add only for admin middleware + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + $data = ArrayUtils::omit($data, 'collection'); + + $this->enforcePermissions($this->collection, $data, $params); + + // Validates the collection name + $this->validate(['collection' => $name], ['collection' => 'required|string']); + + // Validates payload data + $collectionsCollectionObject = $this->getSchemaManager()->getCollection($this->collection); + $constraints = $this->createConstraintFor($this->collection, $collectionsCollectionObject->getFieldsName()); + $data['collection'] = $name; + $this->validate($data, array_merge(['fields' => 'array'], $constraints)); + + $collectionObject = $this->getSchemaManager()->getCollection($name); + if (!$collectionObject->isManaged()) { + throw new CollectionNotManagedException($collectionObject->getName()); + } + + // TODO: Create a check if exists method (quicker) + not found exception + $tableGateway = $this->createTableGateway($this->collection); + $tableGateway->getOneData($name); + // ---------------------------------------------------------------------------- + + if (!$this->getSchemaManager()->tableExists($name)) { + throw new CollectionNotFoundException($name); + } + + $collection = $this->getSchemaManager()->getCollection($name); + $fields = ArrayUtils::get($data, 'fields', []); + foreach ($fields as $i => $field) { + $field = $collection->getField($field['field']); + if ($field) { + $currentColumnData = $field->toArray(); + $fields[$i] = array_merge($currentColumnData, $fields[$i]); + } + } + + $data['fields'] = $fields; + $success = $this->updateTableSchema($collection, $data); + if (!$success) { + throw new ErrorException('Error updating the collection'); + } + + $collectionsTableGateway = $this->createTableGateway('directus_collections'); + if (!empty($fields)) { + $this->addColumnsInfo($name, $fields); + } + + $item = ArrayUtils::omit($data, 'fields'); + $item['collection'] = $name; + + $collection = $collectionsTableGateway->updateRecord($item); + + // ---------------------------------------------------------------------------- + return $tableGateway->wrapData( + $collection->toArray(), + true, + ArrayUtils::get($params, 'meta') + ); + } + + public function delete($name, array $params = []) + { + $this->enforcePermissions($this->collection, [], $params); + $this->validate(['name' => $name], ['name' => 'required|string']); + // TODO: How are we going to handle unmanage + // $unmanaged = $request->getQueryParam('unmanage', 0); + // if ($unmanaged == 1) { + // $tableGateway = new RelationalTableGateway($tableName, $dbConnection, $acl); + // $success = $tableGateway->stopManaging(); + // } + + return $this->dropTable($name); + } + + /** + * Adds a column to an existing table + * + * @param string $collectionName + * @param string $columnName + * @param array $data + * @param array $params + * + * @return array + * + * @throws FieldAlreadyExistsException + * @throws CollectionNotFoundException + * @throws UnauthorizedException + */ + public function addColumn($collectionName, $columnName, array $data, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + $this->enforcePermissions('directus_fields', $data, $params); + + $data['field'] = $columnName; + $data['collection'] = $collectionName; + // TODO: Length is required by some data types, which make this validation not working fully for columns + // TODO: Create new constraint that validates the column data type to be one of the list supported + $collectionObject = $this->getSchemaManager()->getCollection('directus_fields'); + $constraints = $this->createConstraintFor('directus_fields', $collectionObject->getFieldsName()); + $this->validate(array_merge($data, ['collection' => $collectionName]), array_merge(['collection' => 'required|string'], $constraints)); + + // ---------------------------------------------------------------------------- + + $collection = $this->getSchemaManager()->getCollection($collectionName); + if (!$collection) { + throw new CollectionNotFoundException($collectionName); + } + + $field = $collection->getField($columnName); + if ($field && $field->isManaged()) { + throw new FieldAlreadyExistsException($columnName); + } + + $columnData = array_merge($data, [ + 'field' => $columnName + ]); + + // TODO: Only call this when necessary + $this->updateTableSchema($collection, [ + 'fields' => [$columnData] + ]); + + // ---------------------------------------------------------------------------- + + $field = $this->addFieldInfo($collectionName, $columnName, $columnData); + + return $this->getFieldsTableGateway()->wrapData( + $field->toArray(), + true, + ArrayUtils::get($params, 'meta', 0) + ); + } + + /** + * Adds a column to an existing table + * + * @param string $collectionName + * @param string $fieldName + * @param array $data + * @param array $params + * + * @return array + * + * @throws FieldNotFoundException + * @throws FieldNotManagedException + * @throws CollectionNotFoundException + * @throws UnauthorizedException + */ + public function changeColumn($collectionName, $fieldName, array $data, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new UnauthorizedException('Permission denied'); + } + + $this->enforcePermissions('directus_fields', $data, $params); + + // TODO: Length is required by some data types, which make this validation not working fully for columns + // TODO: Create new constraint that validates the column data type to be one of the list supported + $this->validate([ + 'collection' => $collectionName, + 'field' => $fieldName, + 'payload' => $data + ], [ + 'collection' => 'required|string', + 'field' => 'required|string', + 'payload' => 'required|array' + ]); + + $this->validatePayload('directus_fields', array_keys($data), $data, $params); + + // Remove field from data + ArrayUtils::pull($data, 'field'); + + // ---------------------------------------------------------------------------- + + $collection = $this->getSchemaManager()->getCollection($collectionName); + if (!$collection) { + throw new CollectionNotFoundException($collectionName); + } + + $field = $collection->getField($fieldName); + if (!$field) { + throw new FieldNotFoundException($fieldName); + } + + if (!$field->isManaged()) { + throw new FieldNotManagedException($field->getName()); + } + + // TODO: Only update schema when is needed + $fieldData = array_merge($field->toArray(), $data); + $this->updateTableSchema($collection, [ + 'fields' => [$fieldData] + ]); + + // $this->invalidateCacheTags(['tableColumnsSchema_'.$tableName, 'columnSchema_'.$tableName.'_'.$columnName]); + $field = $this->addOrUpdateFieldInfo($collectionName, $fieldName, $data); + // ---------------------------------------------------------------------------- + + return $this->getFieldsTableGateway()->wrapData( + $field->toArray(), + true, + ArrayUtils::get($params, 'meta', 0) + ); + } + + /** + * Updates a list of fields with different data each + * + * @param string $collectionName + * @param array $payload + * @param array $params + * + * @return array + * + * @throws InvalidRequestException + */ + public function batchUpdateField($collectionName, array $payload, array $params = []) + { + if (!isset($payload[0]) || !is_array($payload[0])) { + throw new InvalidRequestException('batch update expect an array of items'); + } + + foreach ($payload as $data) { + $this->validatePayload(SchemaManager::COLLECTION_FIELDS, array_keys($data), $data, $params); + $this->validate($data, ['field' => 'required']); + } + + $allItems = []; + foreach ($payload as $data) { + $fieldName = ArrayUtils::get($data, 'field'); + $item = $this->changeColumn($collectionName, $fieldName, $data, $params); + + if (!is_null($item)) { + $allItems[] = $item['data']; + } + } + + if (!empty($allItems)) { + $allItems = $this->getFieldsTableGateway()->wrapData($allItems, false, ArrayUtils::get($params, 'meta')); + } + + return $allItems; + } + + /** + * Updates a list of fields with the same data + * + * @param $collectionName + * @param array $fieldNames + * @param array $payload + * @param array $params + * + * @return array + */ + public function batchUpdateFieldWithIds($collectionName, array $fieldNames, array $payload, array $params = []) + { + $this->validatePayload(SchemaManager::COLLECTION_FIELDS, array_keys($payload), $payload, $params); + $this->validate(['fields' => $fieldNames], ['fields' => 'required']); + + $allItems = []; + foreach ($fieldNames as $fieldName) { + $item = $this->changeColumn($collectionName, $fieldName, $payload, $params); + if (!empty($item)) { + $allItems[] = $item['data']; + } + } + + if (!empty($allItems)) { + $allItems = $this->getFieldsTableGateway()->wrapData($allItems, false, ArrayUtils::get($params, 'meta')); + } + + return $allItems; + } + + public function dropColumn($collectionName, $fieldName) + { + $tableObject = $this->getSchemaManager()->getCollection($collectionName); + if (!$tableObject) { + throw new CollectionNotFoundException($collectionName); + } + + $columnObject = $tableObject->getField($fieldName); + if (!$columnObject) { + throw new FieldNotFoundException($fieldName); + } + + if (count($tableObject->getFields()) === 1) { + throw new BadRequestException('Cannot delete the last field'); + } + + if (!$columnObject->isAlias()) { + if (!$this->dropColumnSchema($collectionName, $fieldName)) { + throw new ErrorException('Error deleting the field'); + } + } + + if ($columnObject->isManaged()) { + $this->removeColumnInfo($collectionName, $fieldName); + } + } + + /** + * Add columns information to the fields table + * + * @param $collectionName + * @param array $columns + * + * @return BaseRowGateway[] + */ + public function addColumnsInfo($collectionName, array $columns) + { + $resultsSet = []; + foreach ($columns as $column) { + $resultsSet[] = $this->addOrUpdateFieldInfo($collectionName, $column['field'], $column); + } + + return $resultsSet; + } + + /** + * Adds or update a field data + * + * @param string $collection + * @param string $field + * @param array $data + * + * @return BaseRowGateway + */ + protected function addOrUpdateFieldInfo($collection, $field, array $data) + { + $fieldsTableGateway = $this->getFieldsTableGateway(); + $row = $fieldsTableGateway->findOneByArray([ + 'collection' => $collection, + 'field' => $field + ]); + + if ($row) { + $field = $this->updateFieldInfo($row['id'], $data); + } else { + $field = $this->addFieldInfo($collection, $field, $data); + } + + return $field; + } + + protected function addFieldInfo($collection, $field, array $data) + { + $defaults = [ + 'collection' => $collection, + 'field' => $field, + 'type' => null, + 'interface' => null, + 'required' => false, + 'sort' => 0, + 'note' => null, + 'hidden_input' => 0, + 'hidden_list' => 0, + 'options' => null + ]; + + $data = array_merge($defaults, $data, [ + 'collection' => $collection, + 'field' => $field + ]); + + $collectionObject = $this->getSchemaManager()->getCollection('directus_fields'); + + return $this->getFieldsTableGateway()->updateRecord( + ArrayUtils::pick($data, $collectionObject->getFieldsName()) + ); + } + + protected function updateFieldInfo($id, array $data) + { + ArrayUtils::remove($data, ['collection', 'field']); + $data['id'] = $id; + + $collectionObject = $this->getSchemaManager()->getCollection('directus_fields'); + + return $this->getFieldsTableGateway()->updateRecord( + ArrayUtils::pick($data, $collectionObject->getFieldsName()) + ); + } + + /** + * @param $collectionName + * @param $fieldName + * + * @return int + */ + public function removeColumnInfo($collectionName, $fieldName) + { + $fieldsTableGateway = $this->getFieldsTableGateway(); + + return $fieldsTableGateway->delete([ + 'collection' => $collectionName, + 'field' => $fieldName + ]); + } + + /** + * @param $collectionName + * @param $fieldName + * + * @return bool + */ + protected function dropColumnSchema($collectionName, $fieldName) + { + /** @var SchemaFactory $schemaFactory */ + $schemaFactory = $this->container->get('schema_factory'); + $table = $schemaFactory->alterTable($collectionName, [ + 'drop' => [ + $fieldName + ] + ]); + + return $schemaFactory->buildTable($table) ? true : false; + } + + /** + * Drops the given table and its table and columns information + * + * @param $name + * + * @return bool + * + * @throws CollectionNotFoundException + */ + public function dropTable($name) + { + if (!$this->getSchemaManager()->tableExists($name)) { + throw new CollectionNotFoundException($name); + } + + $tableGateway = $this->createTableGateway($name); + + return $tableGateway->drop(); + } + + /** + * Checks whether the given name is a valid clean table name + * + * @param $name + * + * @return bool + */ + public function isValidName($name) + { + $isTableNameAlphanumeric = preg_match("/[a-z0-9]+/i", $name); + $zeroOrMoreUnderscoresDashes = preg_match("/[_-]*/i", $name); + + return $isTableNameAlphanumeric && $zeroOrMoreUnderscoresDashes; + } + + /** + * Gets the table object representation + * + * @param $tableName + * + * @return \Directus\Database\Object\Collection + */ + public function getTableObject($tableName) + { + return SchemaService::getCollection($tableName); + } + + /** + * Checks that at least one of the fields has primary_key set to true. + * + * @param array $fields + * + * @return bool + */ + public function hasPrimaryField(array $fields) + { + return $this->hasFieldAttributeWith($fields, 'primary_key', true, null, 1); + } + + /** + * Checks that a maximum of 1 field has the primary_key field set to true. This will succeed if there are 0 + * or 1 fields set as the primary key. + * + * @param array $fields + * + * @return bool + */ + public function hasUniquePrimaryField(array $fields) + { + return $this->hasFieldAttributeWith($fields, 'primary_key', true); + } + + /** + * Checks that at most one of the fields has auto_increment set to true + * + * @param array $fields + * + * @return bool + */ + public function hasUniqueAutoIncrementField(array $fields) + { + return $this->hasFieldAttributeWith($fields, 'auto_increment', true); + } + + /** + * Checks that all fields name are unique + * + * @param array $fields + * + * @return bool + */ + public function hasUniqueFieldsName(array $fields) + { + $fieldsName = []; + $unique = true; + + foreach ($fields as $field) { + $fieldName = ArrayUtils::get($field, 'field'); + if (in_array($fieldName, $fieldsName)) { + $unique = false; + break; + } + + $fieldsName[] = $fieldName; + } + + return $unique; + } + + /** + * Checks that a set of fields has at least $min and at most $max attribute with the value of $value + * + * @param array $fields + * @param string $attribute + * @param mixed $value + * @param int $max + * @param int $min + * + * @return bool + */ + protected function hasFieldAttributeWith(array $fields, $attribute, $value, $max = 1, $min = 0) + { + $count = 0; + $result = false; + + foreach ($fields as $field) { + if (ArrayUtils::get($field, $attribute) === $value) { + $count++; + } + } + + $ignoreMax = is_null($max); + $ignoreMin = is_null($min); + + if ($ignoreMax && $ignoreMin) { + $result = $count >= 1; + } else if ($ignoreMax) { + $result = $count >= $min; + } else if ($ignoreMin) { + $result = $count <= $max; + } else { + $result = $count >= $min && $count <= $max; + } + + return $result; + } + + + /** + * @param string $name + * @param array $data + * + * @return bool + */ + protected function createTableSchema($name, array $data) + { + /** @var SchemaFactory $schemaFactory */ + $schemaFactory = $this->container->get('schema_factory'); + + $columns = ArrayUtils::get($data, 'fields', []); + $this->validateSystemFields($columns); + $table = $schemaFactory->createTable($name, $columns); + + /** @var Emitter $hookEmitter */ + $hookEmitter = $this->container->get('hook_emitter'); + $hookEmitter->run('collection.create:before', $name); + + $result = $schemaFactory->buildTable($table); + + $hookEmitter->run('collection.create', $name); + $hookEmitter->run('collection.create:after', $name); + + return $result ? true : false; + } + + /** + * @param Collection $collection + * @param array $data + * + * @return bool + */ + protected function updateTableSchema(Collection $collection, array $data) + { + /** @var SchemaFactory $schemaFactory */ + $schemaFactory = $this->container->get('schema_factory'); + $name = $collection->getName(); + + $fields = ArrayUtils::get($data, 'fields', []); + $this->validateSystemFields($fields); + + $toAdd = $toChange = $toDrop = []; + foreach ($fields as $fieldData) { + $field = $collection->getField($fieldData['field']); + + if ($field) { + if (!$field->isAlias() && DataTypes::isAliasType(ArrayUtils::get($fieldData, 'type'))) { + $toDrop[] = $field->getName(); + } else if ($field->isAlias() && !DataTypes::isAliasType(ArrayUtils::get($fieldData, 'type'))) { + $toAdd[] = array_merge($field->toArray(), $fieldData); + } else { + $toChange[] = array_merge($field->toArray(), $fieldData); + } + } else { + $toAdd[] = $fieldData; + } + } + + $table = $schemaFactory->alterTable($name, [ + 'add' => $toAdd, + 'change' => $toChange, + 'drop' => $toDrop + ]); + + /** @var Emitter $hookEmitter */ + $hookEmitter = $this->container->get('hook_emitter'); + $hookEmitter->run('collection.update:before', $name); + + $result = $schemaFactory->buildTable($table); + $this->updateColumnsRelation($name, array_merge($toAdd, $toChange)); + + $hookEmitter->run('collection.update', $name); + $hookEmitter->run('collection.update:after', $name); + + return $result ? true : false; + } + + protected function updateColumnsRelation($collectionName, array $columns) + { + $result = []; + foreach ($columns as $column) { + $result[] = $this->updateColumnRelation($collectionName, $column); + } + + return $result; + } + + protected function updateColumnRelation($collectionName, array $column) + { + $relationData = ArrayUtils::get($column, 'relation', []); + if (!$relationData) { + return false; + } + + $relationshipType = ArrayUtils::get($relationData, 'relationship_type', ''); + $collectionBName = ArrayUtils::get($relationData, 'collection_b'); + $storeCollectionName = ArrayUtils::get($relationData, 'store_collection'); + $collectionBObject = $this->getSchemaManager()->getCollection($collectionBName); + $relationsTableGateway = $this->createTableGateway('directus_relations'); + + $data = []; + switch ($relationshipType) { + case FieldRelationship::MANY_TO_ONE: + $data['relationship_type'] = FieldRelationship::MANY_TO_ONE; + $data['collection_a'] = $collectionName; + $data['collection_b'] = $collectionBName; + $data['store_key_a'] = $column['field']; + $data['store_key_b'] = $collectionBObject->getPrimaryKeyName(); + break; + case FieldRelationship::ONE_TO_MANY: + $data['relationship_type'] = FieldRelationship::ONE_TO_MANY; + $data['collection_a'] = $collectionName; + $data['collection_b'] = $collectionBName; + $data['store_key_a'] = $collectionBObject->getPrimaryKeyName(); + $data['store_key_b'] = $column['field']; + break; + case FieldRelationship::MANY_TO_MANY: + $data['relationship_type'] = FieldRelationship::MANY_TO_MANY; + $data['collection_a'] = $collectionName; + $data['store_collection'] = $storeCollectionName; + $data['collection_b'] = $collectionBName; + $data['store_key_a'] = $relationData['store_key_a']; + $data['store_key_b'] = $relationData['store_key_b']; + break; + } + + + $row = $relationsTableGateway->findOneByArray([ + 'collection_a' => $collectionName, + 'store_key_a' => $column['field'] + ]); + + if ($row) { + $data['id'] = $row['id']; + } + + return $relationsTableGateway->updateRecord($data); + } + + /** + * @param array $columns + * + * @throws InvalidRequestException + */ + protected function validateSystemFields(array $columns) + { + $found = []; + + foreach ($columns as $column) { + $type = ArrayUtils::get($column, 'type'); + if ($this->getSchemaManager()->isUniqueFieldType($type)) { + if (!isset($found[$type])) { + $found[$type] = 0; + } + + $found[$type]++; + } + } + + $types = []; + foreach ($found as $type=> $count) { + if ($count > 1) { + $types[] = $type; + } + } + + if (!empty($types)) { + throw new InvalidRequestException( + 'Only one system field permitted per table: ' . implode(', ', $types) + ); + } + } + + /** + * @param array $columns + * + * @return array + * + * @throws InvalidRequestException + */ + protected function parseColumns(array $columns) + { + $result = []; + foreach ($columns as $column) { + if (!isset($column['type']) || !isset($column['field'])) { + throw new InvalidRequestException( + 'All column requires a name and a type.' + ); + } + + $result[$column['field']] = ArrayUtils::omit($column, 'field'); + } + } + + /** + * Merges a list of missing Schema Attributes into Directus Attributes + * + * @param array $collectionNames + * @param array $collectionsData + * + * @return array + */ + protected function mergeMissingSchemaCollections(array $collectionNames, array $collectionsData) + { + if (!ArrayUtils::isNumericKeys($collectionsData)) { + return $this->mergeSchemaCollection($collectionNames[0], $collectionsData); + } + + $collectionsDataNames = ArrayUtils::pluck($collectionsData, 'collection'); + $missingCollectionNames = ArrayUtils::missing($collectionsDataNames, $collectionNames); + + $collectionsData = $this->mergeSchemaCollections($collectionsData); + + foreach ($missingCollectionNames as $name) { + try { + $collectionsData[] = $this->mergeSchemaCollection($name, []); + } catch (CollectionNotFoundException $e) { + // if the collection doesn't exists don't bother with the exception + // as this is a "filtering" result + // which means getting empty result is okay and expected + } + } + + return $collectionsData; + } + + protected function mergeSchemaCollections(array $collectionsData) + { + foreach ($collectionsData as &$collectionData) { + $collectionData = $this->mergeSchemaCollection($collectionData['collection'], $collectionData); + } + + return $collectionsData; + } + + /** + * Merges a list of missing Schema Attributes into Directus Attributes + * + * @param Collection $collection + * @param array $fieldsData + * @param array $onlyFields + * + * @return array + */ + protected function mergeMissingSchemaFields(Collection $collection, array $fieldsData, array $onlyFields = null) + { + $missingFieldsData = []; + $fieldsName = ArrayUtils::pluck($fieldsData, 'field'); + + $missingFields = $collection->getFieldsNotIn( + $fieldsName + ); + + foreach ($fieldsData as $key => $fieldData) { + $result = $this->mergeMissingSchemaField($collection, $fieldData); + + if ($result) { + $fieldsData[$key] = $result; + } + } + + foreach ($missingFields as $missingField) { + if (!is_array($onlyFields) || in_array($missingField->getName(), $onlyFields)) { + $missingFieldsData[] = $this->mergeSchemaField($missingField); + } + } + + return array_merge($fieldsData, $missingFieldsData); + } + + /** + * Merges a Field data with an Field object + * + * @param Collection $collection + * @param array $fieldData + * + * @return array + */ + protected function mergeMissingSchemaField(Collection $collection, array $fieldData) + { + $field = $collection->getField(ArrayUtils::get($fieldData, 'field')); + + // if for some reason the field key doesn't exists + // continue with everything as if nothing has happened + if (!$field) { + return null; + } + + return $this->mergeSchemaField($field, $fieldData); + } + + /** + * Parses Schema Attributes into Directus Attributes + * + * @param Field $field + * @param array $fieldData + * + * @return array + */ + protected function mergeSchemaField(Field $field, array $fieldData = []) + { + $tableGateway = $this->getFieldsTableGateway(); + $fieldsAttributes = array_merge($tableGateway->getTableSchema()->getFieldsName(), ['managed']); + + $data = ArrayUtils::pick( + array_merge($field->toArray(), $fieldData), + $fieldsAttributes + ); + + // it must be not managed + $data['managed'] = boolval(ArrayUtils::get($data, 'managed')); + $data['primary_key'] = $field->hasPrimaryKey(); + + $result = $tableGateway->parseRecord($data); + + return $result; + } + + /** + * Parses Collection Schema Attributes into Directus Attributes + * + * @param string $collectionName + * @param array $collectionData + * + * @return array + */ + protected function mergeSchemaCollection($collectionName, array $collectionData) + { + /** @var SchemaManager $schemaManager */ + $schemaManager = $this->container->get('schema_manager'); + $collection = $schemaManager->getCollection($collectionName); + $tableGateway = $this->getCollectionsTableGateway(); + $attributesName = array_merge($tableGateway->getTableSchema()->getFieldsName(), ['managed']); + + $collectionData = array_merge( + ArrayUtils::pick($collection->toArray(), $attributesName), + $collectionData + ); + + $collectionData['managed'] = boolval($collectionData['managed']); + + return $tableGateway->parseRecord($collectionData); + } + + /** + * @return RelationalTableGateway + */ + protected function getFieldsTableGateway() + { + if (!$this->fieldsTableGateway) { + $this->fieldsTableGateway = $this->createTableGateway('directus_fields'); + } + + return $this->fieldsTableGateway; + } + + /** + * @return RelationalTableGateway + */ + protected function getCollectionsTableGateway() + { + if (!$this->fieldsTableGateway) { + $this->fieldsTableGateway = $this->createTableGateway('directus_collections'); + } + + return $this->fieldsTableGateway; + } +} diff --git a/src/core/Directus/Services/UsersService.php b/src/core/Directus/Services/UsersService.php new file mode 100644 index 0000000000..124d66ef35 --- /dev/null +++ b/src/core/Directus/Services/UsersService.php @@ -0,0 +1,207 @@ +collection = SchemaManager::COLLECTION_USERS; + $this->itemsService = new ItemsService($this->container); + } + + public function create(array $data, array $params = []) + { + return $this->itemsService->createItem($this->collection, $data, $params); + } + + public function update($id, array $data, array $params = []) + { + return $this->itemsService->update( + $this->collection, + $this->getUserId($id), + $data, + $params + ); + } + + /** + * @param int $id + * @param string $lastPage + * @param array $params + * + * @return array + */ + public function updateLastPage($id, $lastPage, array $params = []) + { + $data = [ + 'last_ip' => get_request_ip(), + 'last_page' => $lastPage, + 'last_access' => DateTimeUtils::nowInUTC()->toString() + ]; + + $this->getTableGateway()->update($data, [ + 'id' => $this->getUserId($id) + ]); + + return $this->find($this->getUserId($id), $params); + } + + public function find($id, array $params = []) + { + return $this->itemsService->find( + $this->collection, + $this->getUserId($id), + $params + ); + } + + public function findByIds($id, array $params = []) + { + return $this->itemsService->findByIds( + $this->collection, + $this->getUserId($id), + $params + ); + } + + public function findOne(array $params = []) + { + return $this->itemsService->findOne( + $this->collection, + $params + ); + } + + public function delete($id, array $params = []) + { + return $this->itemsService->delete( + $this->collection, + $this->getUserId($id), + $params + ); + } + + /** + * @param array $params + * + * @return array + */ + public function findAll(array $params = []) + { + return $this->getItemsAndSetResponseCacheTags($this->getTableGateway(), $params); + } + + public function invite(array $emails, array $params = []) + { + if (!$this->getAcl()->isAdmin()) { + throw new ForbiddenException('Inviting user was denied'); + } + + foreach ($emails as $email) { + $data = ['email' => $email]; + $this->validate($data, ['email' => 'required|email']); + } + + foreach ($emails as $email) { + $this->sendInvitationTo($email); + } + + return $this->findAll([ + 'status' => false, + 'filter' => [ + 'email' => ['in' => $emails] + ] + ]); + } + + /** + * Gets the user table gateway + * + * @return RelationalTableGateway + */ + public function getTableGateway() + { + if (!$this->tableGateway) { + $this->tableGateway = $this->createTableGateway($this->collection); + } + + return $this->tableGateway; + } + + /** + * @param string $email + */ + protected function sendInvitationTo($email) + { + // TODO: Builder/Service to get table gateway + // $usersRepository = $repositoryCollection->get('users'); + // $usersRepository->add(); + $tableGateway = $this->createTableGateway($this->collection); + $user = $tableGateway->findOneBy('email', $email); + + // TODO: Throw exception when email exists + // Probably resend if the email exists? + // TODO: Add activity + if (!$user) { + /** @var Provider $auth */ + $auth = $this->container->get('auth'); + $invitationToken = $auth->generateInvitationToken([ + 'date' => DateTimeUtils::nowInUTC()->toString(), + 'sender' => $this->getAcl()->getUserId() + ]); + + $result = $tableGateway->insert([ + 'status' => DirectusUsersTableGateway::STATUS_DISABLED, + 'email' => $email, + 'invite_token' => $invitationToken, + 'invite_accepted' => 0 + ]); + + if ($result) { + // TODO: This should be a moved to a hook + send_user_invitation_email($email, $invitationToken); + } + } + } + + /** + * Replace "me" with the authenticated user + * + * @param null $id + * + * @return int|null + */ + protected function getUserId($id = null) + { + if ($id === 'me') { + $id = $this->getAcl()->getUserId(); + } + + return $id; + } +} diff --git a/src/core/Directus/Services/UtilsService.php b/src/core/Directus/Services/UtilsService.php new file mode 100644 index 0000000000..32232cb3fe --- /dev/null +++ b/src/core/Directus/Services/UtilsService.php @@ -0,0 +1,65 @@ +validate([ + 'string' => $string + ], [ + 'string' => 'required|string' + ]); + + $options['hasher'] = $hasher; + /** @var HashManager $hashManager */ + $hashManager = $this->container->get('hash_manager'); + $hashedString = $hashManager->hash($string, $options); + + return [ + 'data' => [ + 'hash' => $hashedString + ] + ]; + } + + public function verifyHashString($string, $hash, $hasher, array $options = []) + { + $this->validate([ + 'string' => $string, + 'hash' => $hash + ], [ + 'string' => 'required|string', + 'hash' => 'required|string' + ]); + + $options['hasher'] = $hasher; + /** @var HashManager $hashManager */ + $hashManager = $this->container->get('hash_manager'); + $valid = $hashManager->verify($string, $hash, $options); + + return [ + 'data' => [ + 'valid' => $valid + ] + ]; + } + + public function randomString($length, $options = []) + { + $this->validate(['length' => $length], ['length' => 'numeric']); + + // TODO: Add more options + $randomString = StringUtils::randomString($length); + + return [ + 'data' => [ + 'random' => $randomString + ] + ]; + } +} diff --git a/src/core/Directus/Session/Session.php b/src/core/Directus/Session/Session.php new file mode 100644 index 0000000000..0a9581cb69 --- /dev/null +++ b/src/core/Directus/Session/Session.php @@ -0,0 +1,48 @@ +storage = $storage; + } + + /** + * Get Session storage + * + * @return SessionStorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Proxy all undefined method to the storage + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return call_user_func_array([$this->storage, $method], $arguments); + } +} diff --git a/src/core/Directus/Session/Storage/ArraySessionStorage.php b/src/core/Directus/Session/Storage/ArraySessionStorage.php new file mode 100644 index 0000000000..5110d9f943 --- /dev/null +++ b/src/core/Directus/Session/Storage/ArraySessionStorage.php @@ -0,0 +1,128 @@ +sessionName = $options['name']; + } + + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->sessionName; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->isStarted()) { + return false; + } + + $this->started = true; + + return $this->started; + } + + /** + * {@inheritdoc} + */ + public function stop() + { + if ($this->isStarted()) { + $this->started = false; + } + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return (bool) $this->started; + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + $key = (string) $key; + + return array_key_exists($key, $this->items) ? $this->items[$key] : null; + } + + public function getItems() + { + return $this->items; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + $this->items[(string) $key] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + unset($this->items[(string) $key]); + } + + public function clear() + { + $this->items = []; + } +} diff --git a/src/core/Directus/Session/Storage/NativeSessionStorage.php b/src/core/Directus/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000000..9392806654 --- /dev/null +++ b/src/core/Directus/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,150 @@ +sessionName = $options['name']; + session_name($this->sessionName); + } + + $this->options = $options; + $cacheLimiter = ''; + if (array_key_exists('cache_limiter', $this->options)) { + $cacheLimiter = $this->options['cache_limiter']; + } + + session_cache_limiter($cacheLimiter); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->sessionName; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->isStarted()) { + return false; + } + + if (PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Session has already started'); + } + + if (!session_start()) { + throw new \RuntimeException('Session start failed'); + } + + $this->started = true; + $this->items = &$_SESSION; + + return $this->started; + } + + /** + * {@inheritdoc} + */ + public function stop() + { + if ($this->isStarted()) { + session_destroy(); + session_write_close(); + $this->started = false; + } + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + if (PHP_SESSION_ACTIVE === session_status()) { + $this->started = true; + } + + return (bool) $this->started; + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + $key = (string) $key; + + return array_key_exists($key, $this->items) ? $this->items[$key] : null; + } + + public function getItems() + { + return $this->items; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + $this->items[(string) $key] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + unset($this->items[(string) $key]); + } + + public function clear() + { + $this->items = []; + } +} diff --git a/src/core/Directus/Session/Storage/SessionStorageInterface.php b/src/core/Directus/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000000..785b84fa64 --- /dev/null +++ b/src/core/Directus/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,76 @@ + 1) { + $key = array_shift($keys); + if (!isset($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + } + + /** + * Get an item from an array + * + * @param array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (strpos($key, '.') !== false) { + $array = static::findDot($array, $key); + + if (static::exists($array, $key)) { + return $array[$key]; + } + } + + return $default; + } + + /** + * Gets and remove an item from the array + * + * @param array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public static function pull(array &$array, $key, $default = null) + { + // TODO: Implement access by separator (example dot-notation) + $value = ArrayUtils::get($array, $key, $default); + + ArrayUtils::remove($array, $key); + + return $value; + } + + public static function has($array, $key) + { + if (static::exists($array, $key)) { + return true; + } + + if (strpos($key, '.') === false) { + return false; + } + + $array = static::findDot($array, $key); + + return static::exists($array, $key); + } + + public static function exists($array, $key) + { + return array_key_exists($key, $array); + } + + /** + * Filter an array by keys + * @param $array + * @param $keys + * @param bool $omit + * @return array + */ + public static function filterByKey($array, $keys, $omit = false) + { + $result = []; + + if (is_string($keys)) { + $keys = [$keys]; + } + + foreach ($array as $key => $value) { + $condition = in_array($key, $keys); + if ($omit) { + $condition = !$condition; + } + + if ($condition) { + $result[$key] = $value; + } + } + + return $result; + } + + /** + * Return a copy of the object, filtered to only have values for the whitelisted keys (or array of valid keys). + * @param array $array + * @param string|array $keys + * @return array + */ + public static function pick($array, $keys) + { + return static::filterByKey($array, $keys); + } + + /** + * Return a copy of the object, filtered to omit values for the blacklisted keys (or array of valid keys). + * @param array $array + * @param string|array $keys + * @return array + */ + public static function omit($array, $keys) + { + return static::filterByKey($array, $keys, true); + } + + /** + * Return whether or not a set of keys exists in an array + * + * @param array $array + * @param array|mixed $keys + * + * @return bool + */ + public static function contains(array $array, $keys) + { + if (!is_array($keys)) { + $keys = [$keys]; + } + + foreach ($keys as $key) { + if (!array_key_exists($key, $array)) { + return false; + } + } + + return true; + } + + /** + * Checks whether the given array contain at least one of the given keys + * + * @param array $array + * @param array $keys + * + * @return bool + */ + public static function containsSome(array $array, array $keys) + { + foreach($keys as $key) { + if (static::has($array, $key)) { + return true; + } + } + + return false; + } + + /** + * Flatten a multi-dimensional associative array with a dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + return static::flatKey('.', $array, $prepend); + } + + /** + * Find a the value of an array based on a relational key (nested value) + * + * This is a better option than using dot + * Dot flatten ALL keys which make thing slower when the array is big + * to just find one value + * + * @param $array + * @param $key + * + * @return array + */ + public static function findDot($array, $key) + { + $result = static::findFlatKey('.', $array, $key); + + return $result ? [$result['key'] => $result['value']] : []; + } + + /** + * Flatten a multi-dimensional associative array with a character. + * + * @param string $separator + * @param array $array + * @param string $prepend + * @return array + */ + public static function flatKey($separator, $array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::flatKey($separator, $value, $prepend . $key . $separator)); + } + + $results[$prepend . $key] = $value; + } + + return $results; + } + + /** + * Find the nested value of an array using the given separator-notation key + * + * @param $separator + * @param $array + * @param $key + * + * @return array|null + */ + public static function findFlatKey($separator, $array, $key) + { + $keysPath = []; + $result = null; + + if (strpos($key, $separator) !== false) { + $keys = explode($separator, $key); + $value = $array; + + while ($keys) { + $k = array_shift($keys); + + if (!array_key_exists($k, $value)) { + break; + } + + $value = $value[$k]; + $keysPath[] = $k; + + if ($key == implode($separator, $keysPath)) { + $result = [ + 'key' => $key, + 'value' => $value + ]; + } + + // stop the search if the next value is not an array + if (!is_array($value)) { + break; + } + } + } + + return $result; + } + + /** + * Gets the missing values from a array in another array + * + * @param array $from + * @param array $target + * + * @return array + */ + public static function missing(array $from, array $target) + { + return static::intersection($from, $target, true); + } + + /** + * Gets the missing values from a array in another array + * + * Alias of ArrayUtils::missing + * + * @param array $from + * @param array $target + * + * @return array + */ + public static function without(array $from, array $target) + { + return static::missing($from, $target); + } + + /** + * Gets only the values that exists in both array + * + * @param array $arrayOne + * @param array $arrayTwo + * @param bool $without + * + * @return array + */ + public static function intersection(array $arrayOne, array $arrayTwo, $without = false) + { + $values= []; + + foreach($arrayTwo as $value) { + if (in_array($value, $arrayOne) === !$without) { + $values[] = $value; + } + } + + return $values; + } + + /** + * Checks whether the given array has only numeric keys + * + * @param array $array + * + * @return bool + */ + public static function isNumericKeys(array $array) + { + foreach (array_keys($array) as $key) { + if (!is_numeric($key)) { + return false; + } + } + + return true; + } + + /** + * Sets or updates the keys in the source array into the default array + * + * @param array $defaultArray + * @param array $sourceArray + * + * @return array + * + * @link http://php.net/manual/es/function.array-merge-recursive.php#92195 + */ + public static function defaults(array $defaultArray, array $sourceArray) + { + $newArray = $defaultArray; + foreach ($sourceArray as $key => $value) { + if (is_array($value) && array_key_exists($key, $defaultArray) && is_array($defaultArray[$key])) { + $newArray[$key] = static::defaults($newArray[$key], $value); + } else { + $newArray[$key] = $value; + } + } + + return $newArray; + } + + /** + * Gets a new array changing some or all of the given array keys + * + * @param array $array + * @param array $aliases + * + * @return array + */ + public static function aliasKeys(array $array, array $aliases) + { + $newArray = []; + foreach($array as $key => $value) { + if (in_array($key, $aliases)) { + $newArray[array_search($key, $aliases)] = $value; + } else { + $newArray[$key] = $value; + } + } + + return $newArray; + } + + /** + * Renames a key + * + * @param array $array + * @param string $from + * @param string $to + */ + public static function rename(array &$array, $from, $to) + { + if (ArrayUtils::exists($array, $from)) { + $value = ArrayUtils::get($array, $from); + + $array[$to] = $value; + + ArrayUtils::remove($array, $from); + } + } + + /** + * Rename a list of keys + * + * @param array $array + * @param $keys + */ + public static function renameSome(array &$array, $keys) + { + foreach ($keys as $from => $to) { + static::rename($array, $from, $to); + } + } + + // /** + // * Swaps element values + // * + // * @param array $array + // * @param string $from + // * @param string $to + // * + // * @return int + // */ + // public static function swap(array &$array, $from, $to) + // { + // TODO: Swap values + // if (!isset($array[$to])) { + // static::rename($array, $from, $to); + // } else { + // $temp = ArrayUtils::get($array, $from); + // $array[$from] = ArrayUtils::get($array, $to); + // $array[$to] = $temp; + // } + // } + + /** + * Pushes a new element at the end of the given array + * + * @param array $array + * @param $value + * + * @return int + */ + public static function push(array &$array, $value) + { + return array_push($array, $value); + } + + /** + * Pulls the last element from the given array + * + * @param array $array + * + * @return mixed + */ + public static function pop(array &$array) + { + return array_pop($array); + } + + /** + * Pulls the first element from the given array + * + * @param array $array + * + * @return mixed + */ + public static function shift(array &$array) + { + return array_shift($array); + } + + /** + * Adds a new element at the beginning of the given array + * + * @param array $array + * + * @return mixed + */ + public static function unshift(array &$array, $value) + { + return array_unshift($array, $value); + } + + /** + * Removes one or more key from the given array + * + * @param array $array + * @param $keys + */ + public static function remove(array &$array, $keys) + { + if (!is_array($keys)) { + $keys = [$keys]; + } + + foreach($keys as $key) { + unset($array[$key]); + } + } + + /** + * Gets how deep is the given array + * + * 0 = no child + * + * @param array $array + * + * @return int + */ + public static function deepLevel($array) + { + $depth = 0; + + foreach ($array as $value) { + if (is_array($value)) { + $depth = max(static::deepLevel($value) + 1, $depth); + } + } + + return $depth; + } + + /** + * Extract the key from a list of array + * + * @param array $array + * @param string $key + * + * @return array + */ + public static function pluck(array $array, $key) + { + return array_map(function ($value) use ($key) { + return is_array($value) ? static::get($value, $key) : null; + }, $array); + } + + /* + * Creates an array from CSV value + * + * @param mixed $value + * + * @return array + */ + public static function createFromCSV($value) + { + if (is_array($value)) { + return $value; + } + + if (is_string($value)) { + return StringUtils::csv($value); + } + + return []; + } +} diff --git a/src/core/Directus/Util/DateTimeUtils.php b/src/core/Directus/Util/DateTimeUtils.php new file mode 100644 index 0000000000..309b25ca21 --- /dev/null +++ b/src/core/Directus/Util/DateTimeUtils.php @@ -0,0 +1,267 @@ +createTimeZone($timezone); + + parent::__construct($time, $timezone); + + if ($time === null) { + $this->setTimestamp(time()); + } + } + + /** + * Gets the current datetime + * + * @param null $timezone + * + * @return DateTimeUtils + */ + public static function now($timezone = null) + { + return new static(null, $timezone); + } + + public static function nowInUTC() + { + return static::now('UTC'); + } + + /** + * Creates a new DateTimeUtils instance from a \DateTime instance + * + * @param \DateTime $dateTime + * + * @return DateTimeUtils + */ + public static function createFromDateTime(\DateTime $dateTime) + { + return new static($dateTime->format(static::DEFAULT_DATETIME_FORMAT), $dateTime->getTimezone()); + } + + /** + * @param null $time + * @param null $timezone + * + * @return DateTimeUtils + */ + public static function createFromDefaultFormat($time = null, $timezone = null) + { + return static::createDateFromFormat(static::DEFAULT_DATETIME_FORMAT, $time, $timezone); + } + + /** + * @param string $format + * @param string $time + * @param null $timezone + * + * @return DateTimeUtils + */ + public static function createDateFromFormat($format, $time, $timezone = null) + { + $instance = parent::createFromFormat($format, $time, static::createTimeZone($timezone)); + + return static::createFromDateTime($instance); + } + + /** + * @param int $days + * + * @return DateTimeUtils + */ + public static function inDays($days) + { + return static::now()->addDays($days); + } + + /** + * @param int $days + * + * @return DateTimeUtils + */ + public static function wasDays($days) + { + return static::now()->subDays($days); + } + + /** + * Creates a timezone object + * + * @param string|DateTimeZone $timezone + * + * @return DateTimeZone + */ + public static function createTimeZone($timezone) + { + if ($timezone instanceof DateTimeZone) { + return $timezone; + } + + if ($timezone === null) { + return new DateTimeZone(date_default_timezone_get()); + } + + try { + $timezone = new DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \InvalidArgumentException( + sprintf('Unknown or bad timezone (%s)', $timezone) + ); + } + + return $timezone; + } + + /** + * Switch date time to a different timezone + * + * @param $timezone + * + * @return DateTimeUtils + */ + public function switchToTimeZone($timezone) + { + $this->setTimezone($this->createTimeZone($timezone)); + + return $this; + } + + /** + * Add days to the current date time + * + * @param int $days + * + * @return DateTimeUtils + */ + public function addDays($days) + { + return $this->modify(sprintf(static::ADD_DAYS_FORMAT, (int)$days)); + } + + /** + * Subtract days from teh current date time + * + * @param int $days + * + * @return DateTimeUtils + */ + public function subDays($days) + { + return $this->modify(sprintf(static::SUB_DAYS_FORMAT, (int)$days)); + } + + /** + * Gets the datetime in GMT + * + * @return DateTimeUtils + */ + public function toGMT() + { + return $this->toTimeZone('GMT'); + } + + /** + * Gets the datetime string in GMT + * + * @return string + */ + public function toGMTString() + { + return $this->toGMT()->toString(); + } + + /** + * Returns the datetime in ISO 8601 format + * + * @return string + */ + public function toISO8601Format() + { + return $this->format('c'); + } + + /** + * Returns a string format of the datetime + * + * @param null $format + * + * @return string + */ + public function toString($format = null) + { + if ($format === null) { + $format = static::DEFAULT_DATETIME_FORMAT; + } + + return $this->format($format); + } + + /** + * Creates a copy of the current datetime to a new timezone + * + * @param string|DateTimeZone $timezone + * + * @return DateTimeUtils + */ + public function toTimeZone($timezone) + { + return $this->getCopy()->setTimezone($this->createTimeZone($timezone)); + } + + /** + * Gets a copy of the instance + * + * @return DateTimeUtils + */ + public function getCopy() + { + return clone $this; + } +} diff --git a/src/core/Directus/Util/Formatting.php b/src/core/Directus/Util/Formatting.php new file mode 100644 index 0000000000..7bc8cadd90 --- /dev/null +++ b/src/core/Directus/Util/Formatting.php @@ -0,0 +1,404 @@ += $length)) + break; + $unicode .= chr($value); + $unicode_length++; + } else { + if (count($values) == 0) $num_octets = ($value < 224) ? 2 : 3; + + $values[] = $value; + + if ($length && ($unicode_length + ($num_octets * 3)) > $length) + break; + if (count($values) == $num_octets) { + if ($num_octets == 3) { + $unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]) . '%' . dechex($values[2]); + $unicode_length += 9; + } else { + $unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]); + $unicode_length += 6; + } + + $values = []; + $num_octets = 1; + } + } + } + + return $unicode; + } + + /** + * Credit: WORDPRESS 3.6-BETA3 + * http://wpseek.com/remove_accents/ + * @param string $string Text that might have accent characters + * @return string Filtered string with replaced "nice" characters. + * + public static function remove_accents($string) + { + if (!preg_match('/[\x80-\xff]/', $string)) + return $string; + + if (self::seems_utf8($string)) { + $chars = [ + // Decompositions for Latin-1 Supplement + chr(194) . chr(170) => 'a', chr(194) . chr(186) => 'o', + chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A', + chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A', + chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A', + chr(195) . chr(134) => 'AE', chr(195) . chr(135) => 'C', + chr(195) . chr(136) => 'E', chr(195) . chr(137) => 'E', + chr(195) . chr(138) => 'E', chr(195) . chr(139) => 'E', + chr(195) . chr(140) => 'I', chr(195) . chr(141) => 'I', + chr(195) . chr(142) => 'I', chr(195) . chr(143) => 'I', + chr(195) . chr(144) => 'D', chr(195) . chr(145) => 'N', + chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O', + chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O', + chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U', + chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U', + chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y', + chr(195) . chr(158) => 'TH', chr(195) . chr(159) => 's', + chr(195) . chr(160) => 'a', chr(195) . chr(161) => 'a', + chr(195) . chr(162) => 'a', chr(195) . chr(163) => 'a', + chr(195) . chr(164) => 'a', chr(195) . chr(165) => 'a', + chr(195) . chr(166) => 'ae', chr(195) . chr(167) => 'c', + chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e', + chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e', + chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i', + chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i', + chr(195) . chr(176) => 'd', chr(195) . chr(177) => 'n', + chr(195) . chr(178) => 'o', chr(195) . chr(179) => 'o', + chr(195) . chr(180) => 'o', chr(195) . chr(181) => 'o', + chr(195) . chr(182) => 'o', chr(195) . chr(184) => 'o', + chr(195) . chr(185) => 'u', chr(195) . chr(186) => 'u', + chr(195) . chr(187) => 'u', chr(195) . chr(188) => 'u', + chr(195) . chr(189) => 'y', chr(195) . chr(190) => 'th', + chr(195) . chr(191) => 'y', chr(195) . chr(152) => 'O', + // Decompositions for Latin Extended-A + chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a', + chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a', + chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a', + chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c', + chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c', + chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c', + chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c', + chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd', + chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd', + chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e', + chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e', + chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e', + chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e', + chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e', + chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g', + chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g', + chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g', + chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g', + chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h', + chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h', + chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i', + chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i', + chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i', + chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i', + chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i', + chr(196) . chr(178) => 'IJ', chr(196) . chr(179) => 'ij', + chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j', + chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k', + chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L', + chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L', + chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L', + chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L', + chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L', + chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N', + chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N', + chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N', + chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N', + chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N', + chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o', + chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o', + chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o', + chr(197) . chr(146) => 'OE', chr(197) . chr(147) => 'oe', + chr(197) . chr(148) => 'R', chr(197) . chr(149) => 'r', + chr(197) . chr(150) => 'R', chr(197) . chr(151) => 'r', + chr(197) . chr(152) => 'R', chr(197) . chr(153) => 'r', + chr(197) . chr(154) => 'S', chr(197) . chr(155) => 's', + chr(197) . chr(156) => 'S', chr(197) . chr(157) => 's', + chr(197) . chr(158) => 'S', chr(197) . chr(159) => 's', + chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's', + chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't', + chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't', + chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't', + chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u', + chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u', + chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u', + chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u', + chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u', + chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u', + chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w', + chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y', + chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z', + chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z', + chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z', + chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's', + // Decompositions for Latin Extended-B + chr(200) . chr(152) => 'S', chr(200) . chr(153) => 's', + chr(200) . chr(154) => 'T', chr(200) . chr(155) => 't', + // Euro Sign + chr(226) . chr(130) . chr(172) => 'E', + // GBP (Pound) Sign + chr(194) . chr(163) => '', + // Vowels with diacritic (Vietnamese) + // unmarked + chr(198) . chr(160) => 'O', chr(198) . chr(161) => 'o', + chr(198) . chr(175) => 'U', chr(198) . chr(176) => 'u', + // grave accent + chr(225) . chr(186) . chr(166) => 'A', chr(225) . chr(186) . chr(167) => 'a', + chr(225) . chr(186) . chr(176) => 'A', chr(225) . chr(186) . chr(177) => 'a', + chr(225) . chr(187) . chr(128) => 'E', chr(225) . chr(187) . chr(129) => 'e', + chr(225) . chr(187) . chr(146) => 'O', chr(225) . chr(187) . chr(147) => 'o', + chr(225) . chr(187) . chr(156) => 'O', chr(225) . chr(187) . chr(157) => 'o', + chr(225) . chr(187) . chr(170) => 'U', chr(225) . chr(187) . chr(171) => 'u', + chr(225) . chr(187) . chr(178) => 'Y', chr(225) . chr(187) . chr(179) => 'y', + // hook + chr(225) . chr(186) . chr(162) => 'A', chr(225) . chr(186) . chr(163) => 'a', + chr(225) . chr(186) . chr(168) => 'A', chr(225) . chr(186) . chr(169) => 'a', + chr(225) . chr(186) . chr(178) => 'A', chr(225) . chr(186) . chr(179) => 'a', + chr(225) . chr(186) . chr(186) => 'E', chr(225) . chr(186) . chr(187) => 'e', + chr(225) . chr(187) . chr(130) => 'E', chr(225) . chr(187) . chr(131) => 'e', + chr(225) . chr(187) . chr(136) => 'I', chr(225) . chr(187) . chr(137) => 'i', + chr(225) . chr(187) . chr(142) => 'O', chr(225) . chr(187) . chr(143) => 'o', + chr(225) . chr(187) . chr(148) => 'O', chr(225) . chr(187) . chr(149) => 'o', + chr(225) . chr(187) . chr(158) => 'O', chr(225) . chr(187) . chr(159) => 'o', + chr(225) . chr(187) . chr(166) => 'U', chr(225) . chr(187) . chr(167) => 'u', + chr(225) . chr(187) . chr(172) => 'U', chr(225) . chr(187) . chr(173) => 'u', + chr(225) . chr(187) . chr(182) => 'Y', chr(225) . chr(187) . chr(183) => 'y', + // tilde + chr(225) . chr(186) . chr(170) => 'A', chr(225) . chr(186) . chr(171) => 'a', + chr(225) . chr(186) . chr(180) => 'A', chr(225) . chr(186) . chr(181) => 'a', + chr(225) . chr(186) . chr(188) => 'E', chr(225) . chr(186) . chr(189) => 'e', + chr(225) . chr(187) . chr(132) => 'E', chr(225) . chr(187) . chr(133) => 'e', + chr(225) . chr(187) . chr(150) => 'O', chr(225) . chr(187) . chr(151) => 'o', + chr(225) . chr(187) . chr(160) => 'O', chr(225) . chr(187) . chr(161) => 'o', + chr(225) . chr(187) . chr(174) => 'U', chr(225) . chr(187) . chr(175) => 'u', + chr(225) . chr(187) . chr(184) => 'Y', chr(225) . chr(187) . chr(185) => 'y', + // acute accent + chr(225) . chr(186) . chr(164) => 'A', chr(225) . chr(186) . chr(165) => 'a', + chr(225) . chr(186) . chr(174) => 'A', chr(225) . chr(186) . chr(175) => 'a', + chr(225) . chr(186) . chr(190) => 'E', chr(225) . chr(186) . chr(191) => 'e', + chr(225) . chr(187) . chr(144) => 'O', chr(225) . chr(187) . chr(145) => 'o', + chr(225) . chr(187) . chr(154) => 'O', chr(225) . chr(187) . chr(155) => 'o', + chr(225) . chr(187) . chr(168) => 'U', chr(225) . chr(187) . chr(169) => 'u', + // dot below + chr(225) . chr(186) . chr(160) => 'A', chr(225) . chr(186) . chr(161) => 'a', + chr(225) . chr(186) . chr(172) => 'A', chr(225) . chr(186) . chr(173) => 'a', + chr(225) . chr(186) . chr(182) => 'A', chr(225) . chr(186) . chr(183) => 'a', + chr(225) . chr(186) . chr(184) => 'E', chr(225) . chr(186) . chr(185) => 'e', + chr(225) . chr(187) . chr(134) => 'E', chr(225) . chr(187) . chr(135) => 'e', + chr(225) . chr(187) . chr(138) => 'I', chr(225) . chr(187) . chr(139) => 'i', + chr(225) . chr(187) . chr(140) => 'O', chr(225) . chr(187) . chr(141) => 'o', + chr(225) . chr(187) . chr(152) => 'O', chr(225) . chr(187) . chr(153) => 'o', + chr(225) . chr(187) . chr(162) => 'O', chr(225) . chr(187) . chr(163) => 'o', + chr(225) . chr(187) . chr(164) => 'U', chr(225) . chr(187) . chr(165) => 'u', + chr(225) . chr(187) . chr(176) => 'U', chr(225) . chr(187) . chr(177) => 'u', + chr(225) . chr(187) . chr(180) => 'Y', chr(225) . chr(187) . chr(181) => 'y', + // Vowels with diacritic (Chinese, Hanyu Pinyin) + chr(201) . chr(145) => 'a', + // macron + chr(199) . chr(149) => 'U', chr(199) . chr(150) => 'u', + // acute accent + chr(199) . chr(151) => 'U', chr(199) . chr(152) => 'u', + // caron + chr(199) . chr(141) => 'A', chr(199) . chr(142) => 'a', + chr(199) . chr(143) => 'I', chr(199) . chr(144) => 'i', + chr(199) . chr(145) => 'O', chr(199) . chr(146) => 'o', + chr(199) . chr(147) => 'U', chr(199) . chr(148) => 'u', + chr(199) . chr(153) => 'U', chr(199) . chr(154) => 'u', + // grave accent + chr(199) . chr(155) => 'U', chr(199) . chr(156) => 'u', + ]; + + // @TODO: find a substitute for get_locale (get_user_locale?) + // This function is a wordpress function and it doesn't exists in Directus + // Used for locale-specific rules + $locale = get_locale(); + + if ('de_DE' == $locale) { + $chars[chr(195) . chr(132)] = 'Ae'; + $chars[chr(195) . chr(164)] = 'ae'; + $chars[chr(195) . chr(150)] = 'Oe'; + $chars[chr(195) . chr(182)] = 'oe'; + $chars[chr(195) . chr(156)] = 'Ue'; + $chars[chr(195) . chr(188)] = 'ue'; + $chars[chr(195) . chr(159)] = 'ss'; + } + + $string = strtr($string, $chars); + } else { + // Assume ISO-8859-1 if not UTF-8 + $chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158) + . chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194) + . chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202) + . chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210) + . chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218) + . chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227) + . chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235) + . chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243) + . chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251) + . chr(252) . chr(253) . chr(255); + + $chars['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; + + $string = strtr($string, $chars['in'], $chars['out']); + $double_chars['in'] = [chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)]; + $double_chars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; + $string = str_replace($double_chars['in'], $double_chars['out'], $string); + } + + return $string; + }*/ +} diff --git a/src/core/Directus/Util/Git.php b/src/core/Directus/Util/Git.php new file mode 100644 index 0000000000..f8c8f08cf5 --- /dev/null +++ b/src/core/Directus/Util/Git.php @@ -0,0 +1,33 @@ + 'admin@example.com', + 'feedback_token' => sha1(gmdate('U') . StringUtils::randomString(32)), + 'feedback_login' => true, + 'cors_enabled' => false + ], $data); + + $configStub = static::createConfigFileContent($data); + + $configPath = rtrim($path, '/') . '/api.php'; + file_put_contents($configPath, $configStub); + } + + /** + * Replace placeholder wrapped by {{ }} with $data array + * @param string $content + * @param array $data + * @return string + */ + public static function replacePlaceholderValues($content, $data) + { + if (is_array($data)) { + $data = ArrayUtils::dot($data); + } + + $content = StringUtils::replacePlaceholder($content, $data); + + return $content; + } + + /** + * Create Directus Tables from Migrations + * @param string $directusPath + * @throws \Exception + */ + public static function createTables($directusPath) + { + $config = static::getMigrationConfig($directusPath); + + $manager = new Manager($config, new StringInput(''), new NullOutput()); + $manager->migrate('development'); + $manager->seed('development'); + } + + /** + * Run the migration files + * + * @param $directusPath + */ + public static function runMigration($directusPath) + { + $config = static::getMigrationConfig($directusPath); + + $manager = new Manager($config, new StringInput(''), new NullOutput()); + $manager->migrate('development'); + } + + /** + * Run the seeder files + * + * @param $directusPath + */ + public static function runSeeder($directusPath) + { + $config = static::getMigrationConfig($directusPath); + + $manager = new Manager($config, new StringInput(''), new NullOutput()); + $manager->seed('development'); + } + + /** + * Add Directus default settings + * + * @param array $data + * @param string $directusPath + * + * @throws \Exception + */ + public static function addDefaultSettings($data, $directusPath) + { + $directusPath = rtrim($directusPath, '/'); + /** + * Check if configuration files exists + * @throws \InvalidArgumentException + */ + static::checkConfigurationFile($directusPath); + + // require_once $directusPath . '/api/config.php'; + + $app = new Application($directusPath, require $directusPath . '/config/api.php'); + // $db = Bootstrap::get('ZendDb'); + $db = $app->getContainer()->get('database'); + + $defaultSettings = static::getDefaultSettings($data); + + $tableGateway = new TableGateway('directus_settings', $db); + foreach ($defaultSettings as $setting) { + $tableGateway->insert($setting); + } + } + + /** + * Add Directus default user + * + * @param array $data + * @return array + */ + public static function addDefaultUser($data, $directusPath) + { + $app = new Application($directusPath, require $directusPath . '/config/api.php'); + // $db = Bootstrap::get('ZendDb'); + $db = $app->getContainer()->get('database'); + $tableGateway = new TableGateway('directus_users', $db); + + $data = ArrayUtils::defaults([ + 'directus_email' => 'admin@example.com', + 'directus_password' => 'password', + 'directus_token' => 'admin_token' + ], $data); + + $hash = password_hash($data['directus_password'], PASSWORD_DEFAULT, ['cost' => 12]); + + if (!isset($data['directus_token'])) { + $data['directus_token'] = StringUtils::randomString(32); + } + + $tableGateway->insert([ + 'status' => 1, + 'first_name' => 'Admin', + 'last_name' => 'User', + 'email' => $data['directus_email'], + 'password' => $hash, + 'token' => $data['directus_token'], + 'locale' => 'en-US' + ]); + + $userRolesTableGateway = new TableGateway('directus_user_roles', $db); + + $userRolesTableGateway->insert([ + 'user' => $tableGateway->getLastInsertValue(), + 'role' => 1 + ]); + + return $data; + } + + /** + * Check if the given name is schema template. + * @param $name + * @param $directusPath + * @return bool + */ + public static function schemaTemplateExists($name, $directusPath) + { + $directusPath = rtrim($directusPath, '/'); + $schemaTemplatePath = $directusPath . '/api/migrations/templates/' . $name; + + if (!file_exists($schemaTemplatePath)) { + return false; + } + + $isEmpty = count(array_diff(scandir($schemaTemplatePath), ['..', '.'])) > 0 ? false : true; + if (is_readable($schemaTemplatePath) && !$isEmpty) { + return true; + } + + return false; + } + + /** + * Install the given schema template name + * + * @param $name + * @param $directusPath + * + * @throws \Exception + */ + public static function installSchema($name, $directusPath) + { + $directusPath = rtrim($directusPath, '/'); + $templatePath = $directusPath . '/api/migrations/templates/' . $name; + $sqlImportPath = $templatePath . '/import.sql'; + + if (file_exists($sqlImportPath)) { + static::installSchemaFromSQL(file_get_contents($sqlImportPath)); + } else { + static::installSchemaFromMigration($name, $directusPath); + } + } + + /** + * Executes the template migration + * + * @param $name + * @param $directusPath + * + * @throws \Exception + */ + public static function installSchemaFromMigration($name, $directusPath) + { + $directusPath = rtrim($directusPath, '/'); + + /** + * Check if configuration files exists + * @throws \InvalidArgumentException + */ + static::checkConfigurationFile($directusPath); + + // TODO: Install schema templates + } + + /** + * Execute a sql query string + * + * NOTE: This is not recommended at all + * we are doing this because we are trained pro + * soon to be deprecated + * + * @param $sql + * + * @throws \Exception + */ + public static function installSchemaFromSQL($sql) + { + $dbConnection = Application::getInstance()->fromContainer('database'); + + $dbConnection->execute($sql); + } + + /** + * @param $directusPath + * + * @return Config + */ + private static function getMigrationConfig($directusPath) + { + $directusPath = rtrim($directusPath, '/'); + /** + * Check if configuration files exists + * + * @throws \InvalidArgumentException + */ + static::checkConfigurationFile($directusPath); + + $configPath = $directusPath . '/config'; + + $apiConfig = require $configPath . '/api.php'; + $configArray = require $configPath . '/migrations.php'; + $configArray['paths']['migrations'] = $directusPath . '/migrations/db/schemas'; + $configArray['paths']['seeds'] = $directusPath . '/migrations/db/seeds'; + $configArray['environments']['development'] = [ + 'adapter' => ArrayUtils::get($apiConfig, 'database.type'), + 'host' => ArrayUtils::get($apiConfig, 'database.host'), + 'port' => ArrayUtils::get($apiConfig, 'database.port'), + 'name' => ArrayUtils::get($apiConfig, 'database.name'), + 'user' => ArrayUtils::get($apiConfig, 'database.username'), + 'pass' => ArrayUtils::get($apiConfig, 'database.password'), + 'charset' => ArrayUtils::get($apiConfig, 'database.charset', 'utf8') + ]; + + return new Config($configArray); + } + + /** + * Get Directus default settings + * @param $data + * @return array + */ + private static function getDefaultSettings($data) + { + return [ + [ + 'scope' => 'global', + 'key' => 'auto_sign_out', + 'value' => '60' + ], + [ + 'scope' => 'global', + 'key' => 'project_name', + 'value' => isset($data['directus_name']) ? $data['directus_name'] : 'Directus' + ], + [ + 'scope' => 'global', + 'key' => 'default_limit', + 'value' => '200' + ], + [ + 'scope' => 'global', + 'key' => 'logo', + 'value' => '' + ], + [ + 'scope' => 'files', + 'key' => 'file_naming', + 'value' => 'file_id' + ], + [ + 'scope' => 'files', + 'key' => 'youtube_api_key', + 'value' => '' + ] + ]; + } + + /** + * Check if config and configuration file exists + * @param $directusPath + * @throws \Exception + */ + private static function checkConfigurationFile($directusPath) + { + $directusPath = rtrim($directusPath, '/'); + if (!file_exists($directusPath . '/config/api.php')) { + throw new \Exception('Config file does not exists, run [directus config]'); + } + + if (!file_exists($directusPath . '/config/migrations.php')) { + throw new \Exception('Migration configuration file does not exists'); + } + } +} diff --git a/src/core/Directus/Util/Installation/stubs/config.stub b/src/core/Directus/Util/Installation/stubs/config.stub new file mode 100644 index 0000000000..75440bdcdd --- /dev/null +++ b/src/core/Directus/Util/Installation/stubs/config.stub @@ -0,0 +1,138 @@ + [ + 'env' => 'development', + 'timezone' => 'America/New_York', + ], + + 'settings' => [ + 'logger' => [ + 'path' => __DIR__ . '/logs/app.log', + ], + ], + + 'database' => [ + 'type' => '{{db_type}}', + 'host' => '{{db_host}}', + 'port' => {{db_port}}, + 'name' => '{{db_name}}', + 'username' => '{{db_user}}', + 'password' => '{{db_password}}', + 'engine' => 'InnoDB', + 'charset' => 'utf8mb4' + ], + + 'cache' => [ + 'enabled' => false, + 'response_ttl' => 3600, // seconds + // 'pool' => [ + // 'adapter' => 'apc' + // ], + // 'pool' => [ + // 'adapter' => 'apcu' + // ], + // 'pool' => [ + // 'adapter' => 'filesystem', + // 'path' => 'cache', // relative to the api directory + // ], + // 'pool' => [ + // 'adapter' => 'memcached', + // 'host' => 'localhost', + // 'port' => 11211 + // ], + // 'pool' => [ + // 'adapter' => 'redis', + // 'host' => 'localhost', + // 'port' => 6379 + // ], + ], + + 'filesystem' => [ + 'adapter' => 'local', + // By default media directory are located at the same level of directus root + // To make them a level up outside the root directory + // use this instead + // Ex: 'root' => realpath(ROOT_PATH.'/../storage/uploads'), + // Note: ROOT_PATH constant doesn't end with trailing slash + 'root' => 'storage/uploads', + // This is the url where all the media will be pointing to + // here all assets will be (yourdomain)/storage/uploads + // same with thumbnails (yourdomain)/storage/uploads/thumbs + 'root_url' => '/storage/uploads', + // 'key' => 's3-key', + // 'secret' => 's3-secret', + // 'region' => 's3-region', + // 'version' => 's3-version', + // 'bucket' => 's3-bucket' + ], + + 'mail' => [ + 'default' => [ + 'adapter' => 'swift_mailer', + 'transport' => 'mail', + 'from' => '{{directus_email}}' + ], + ], + + 'cors' => [ + 'enabled' => {{cors_enabled}}, + 'origin' => ['*'], + 'headers' => [ + ['Access-Control-Allow-Headers', 'Authorization, Content-Type, Access-Control-Allow-Origin'], + ['Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE'], + ['Access-Control-Allow-Credentials', 'false'] + ] + ], + + 'rate_limit' => [ + 'enabled' => false, + 'limit' => 100, // number of request + 'interval' => 60, // seconds + 'adapter' => 'redis', + 'host' => '127.0.0.1', + 'port' => 6379, + 'timeout' => 10 + ], + + 'hooks' => [], + + 'filters' => [], + + 'feedback' => [ + 'token' => '{{feedback_token}}', + 'login' => {{feedback_login}} + ], + + // These tables will not be loaded in the directus schema + 'tableBlacklist' => [], + + 'auth' => [ + 'secret_key' => '', + 'social_providers' => [ + // 'okta' => [ + // 'client_id' => '', + // 'client_secret' => '', + // 'base_url' => 'https://dev-000000.oktapreview.com/oauth2/default' + // ], + // 'github' => [ + // 'client_id' => '', + // 'client_secret' => '' + // ], + // 'facebook' => [ + // 'client_id' => '', + // 'client_secret' => '', + // 'graph_api_version' => 'v2.8', + // ], + // 'google' => [ + // 'client_id' => '', + // 'client_secret' => '', + // 'hosted_domain' => '*', + // ], + // 'twitter' => [ + // 'identifier' => '', + // 'secret' => '' + // ] + ] + ], +]; diff --git a/src/core/Directus/Util/JWTUtils.php b/src/core/Directus/Util/JWTUtils.php new file mode 100644 index 0000000000..2def25d45b --- /dev/null +++ b/src/core/Directus/Util/JWTUtils.php @@ -0,0 +1,144 @@ +getMessage()) { + case 'Expired token': + $exception = new ExpiredTokenException(); + break; + default: + $exception = new InvalidTokenException(); + } + + throw $exception; + } + + return $payload; + } + + /** + * @param array|object $payload + * @param string $key + * @param string $alg + * @param null $keyId + * @param null $head + * + * @return string + */ + public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) + { + return JWT::encode($payload, $key, $alg, $keyId, $head); + } + + /** + * Checks whether a token is a JWT token + * + * @param $token + * + * @return bool + */ + public static function isJWT($token) + { + if (!is_string($token)) { + return false; + } + + $parts = explode('.', $token); + if (count($parts) != 3) { + return false; + } + + list($headb64, $bodyb64, $cryptob64) = $parts; + if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) { + return false; + } + + return $header->typ === 'JWT'; + } + + /** + * Checks whether ot not the payload has the given type + * + * @param object $payload + * @param string $type + * + * @return bool + */ + public static function hasPayloadType($type, $payload) + { + if (is_object($payload) && property_exists($payload, 'type') && $payload->type === $type) { + return true; + } + + return false; + } + + /** + * Get the token payload object + * + * @param $token + * + * @return null|object + */ + public static function getPayload($token) + { + if (!is_string($token)) { + return null; + } + + $parts = explode('.', $token); + if (count($parts) != 3) { + return null; + } + + list($headb64, $bodyb64, $cryptob64) = $parts; + + return JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64)); + } + + /** + * Checks whether the token has expired + * + * @param $token + * + * @return bool|null + */ + public static function hasExpired($token) + { + $expired = null; + $payload = static::getPayload($token); + + if ($payload && isset($payload->exp)) { + $expired = time() >= $payload->exp; + } + + return $expired; + } +} diff --git a/src/core/Directus/Util/README.md b/src/core/Directus/Util/README.md new file mode 100644 index 0000000000..1c70692c88 --- /dev/null +++ b/src/core/Directus/Util/README.md @@ -0,0 +1 @@ +# Directus Utils diff --git a/src/core/Directus/Util/SchemaUtils.php b/src/core/Directus/Util/SchemaUtils.php new file mode 100644 index 0000000000..0ee54616d9 --- /dev/null +++ b/src/core/Directus/Util/SchemaUtils.php @@ -0,0 +1,36 @@ += 0&& strpos($haystack, $needle, $temp) !== false); + } + + /** + * Return the length of the given string. + * + * @param string $value + * @return int + */ + public static function length($value) + { + return mb_strlen($value); + } + + /** + * Generate a "random" alpha-numeric string. + * + * From Laravel + * @param int $length + * @return string + */ + public static function random($length = 16) + { + $length = (int)$length; + if ($length <= 0) { + throw new \InvalidArgumentException('Random length must be greater than zero'); + } + + if (function_exists('random_bytes')) { + try { + $random = random_bytes($length); + } catch (\Exception $e) { + $random = static::randomString($length); + } + } else if (function_exists('openssl_random_pseudo_bytes')) { + $string = ''; + while (($len = strlen($string)) < $length) { + $size = $length - $len; + $bytes = openssl_random_pseudo_bytes($size); + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + $random = $string; + } else { + $random = static::randomString($length); + } + + return $random; + } + + /** + * Random string shuffled from a list of alphanumeric characters + * + * @param int $length + * + * @return string + */ + public static function randomString($length = 16) + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); + } + + /** + * Convert a string separated by $separator to camel case. + * + * @param $string + * @param bool $first + * @param string $separator + * + * @return string + */ + public static function toCamelCase($string, $first = false, $separator = '_') + { + $parts = explode($separator, $string); + $newParts = array_map(function ($string) { + return ucwords($string); + }, $parts); + + $newString = implode('', $newParts); + + if ($first === false) { + $newString[0] = strtolower($newString[0]); + } + + return $newString; + } + + /** + * Convert a string separated by underscore to camel case. + * + * @param $string + * @param bool $first + * + * @return string + */ + public static function underscoreToCamelCase($string, $first = false) + { + return static::toCamelCase($string, $first); + } + + /** + * Returns the next sequence for a string + * + * @param string $chars + * @return string + */ + public static function charSequence($chars = '') + { + if (!$chars) { + return 'a'; + } + + return ++$chars; + } + + /** + * Replace a string placeholder with the given data. + * + * @param $string + * @param array $data + * @param string $placeHolderFormat + * + * @return string + */ + public static function replacePlaceholder($string, $data = [], $placeHolderFormat = self::PLACEHOLDER_DOUBLE_MUSTACHE) + { + foreach ($data as $key => $value) { + if (is_bool($value)) { + $value = $value ? 'true' : 'false'; + } + + if (is_scalar($value)) { + $string = str_replace(sprintf($placeHolderFormat, $key), $value, $string); + } + } + + return $string; + } + + /** + * Split an csv string into array + * + * @param string $csv + * @param bool $trim + * + * @return array + */ + public static function csv($csv, $trim = true) + { + if (!is_string($csv)) { + throw new \InvalidArgumentException('$cvs must be a string'); + } + + $array = explode(',', $csv); + + if ($trim) { + $array = array_map('trim', $array); + } + + return $array; + } +} diff --git a/src/core/Directus/Util/composer.json b/src/core/Directus/Util/composer.json new file mode 100644 index 0000000000..5b36c1fa21 --- /dev/null +++ b/src/core/Directus/Util/composer.json @@ -0,0 +1,23 @@ +{ + "name": "directus/utils", + "description": "Directus Utils", + "keywords": [ + "directus", + "utils", + "helpers" + ], + "license": "MIT", + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "Directus\\Util\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-d64": "0.9.x-dev" + } + } +} diff --git a/src/core/Directus/Validator/Constraints/Required.php b/src/core/Directus/Validator/Constraints/Required.php new file mode 100644 index 0000000000..df20294506 --- /dev/null +++ b/src/core/Directus/Validator/Constraints/Required.php @@ -0,0 +1,10 @@ +context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->addViolation(); + } + } +} diff --git a/src/core/Directus/Validator/Exception/InvalidRequestException.php b/src/core/Directus/Validator/Exception/InvalidRequestException.php new file mode 100644 index 0000000000..737a04f9e2 --- /dev/null +++ b/src/core/Directus/Validator/Exception/InvalidRequestException.php @@ -0,0 +1,10 @@ +provider = Validation::createValidator(); + } + + /** + * @param mixed $value + * @param array $constraints + * + * @return \Symfony\Component\Validator\ConstraintViolationListInterface + */ + public function validate($value, array $constraints) + { + // TODO: Support OR validation + // Ex. value must be Numeric or string type (Scalar without boolean) + return $this->provider->validate($value, $this->createConstraintFromList($constraints)); + } + + /** + * @return \Symfony\Component\Validator\Validator\ValidatorInterface + */ + public function getProvider() + { + return $this->provider; + } + + /** + * Creates constraints object from name + * + * @param array $constraints + * + * @return Constraint[] + */ + protected function createConstraintFromList(array $constraints) + { + $constraintsObjects = []; + + foreach ($constraints as $constraint) { + $constraintsObjects[] = $this->getConstraint($constraint); + } + + return $constraintsObjects; + } + + /** + * Gets constraint with the given name + * + * @param $name + * @return null|Constraint + * + * @throws UnknownConstraintException + */ + protected function getConstraint($name) + { + $constraint = null; + + switch ($name) { + case 'required': + $constraint = new Required(); + break; + case 'email': + $constraint = new Email(); + break; + case 'json': + $constraint = new Type(['type' => 'array', 'message' => 'This value should be of type json.']); + break; + case 'array': + case 'numeric': + case 'string': + $constraint = new Type(['type' => $name]); + break; + default: + throw new UnknownConstraintException(sprintf('Unknown "%s" constraint', $name)); + } + + return $constraint; + } +} diff --git a/src/endpoints/Activity.php b/src/endpoints/Activity.php new file mode 100644 index 0000000000..976cc36fe4 --- /dev/null +++ b/src/endpoints/Activity.php @@ -0,0 +1,109 @@ +get('', [$this, 'all']); + $app->get('/{id}', [$this, 'read']); + $app->post('/comment', [$this, 'createComment']); + $app->patch('/comment/{id}', [$this, 'updateComment']); + $app->delete('/comment/{id}', [$this, 'deleteComment']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new ActivityService($this->container); + $responseData = $service->findAll($request->getQueryParams()); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new ActivityService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + ArrayUtils::pick($request->getQueryParams(), ['fields', 'meta']) + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function createComment(Request $request, Response $response) + { + $service = new ActivityService($this->container); + $responseData = $service->createComment( + $request->getParsedBody() ?: [], + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function updateComment(Request $request, Response $response) + { + $service = new ActivityService($this->container); + $responseData = $service->updateComment( + $request->getAttribute('id'), + $request->getParsedBodyParam('comment'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function deleteComment(Request $request, Response $response) + { + $service = new ActivityService($this->container); + $service->deleteComment( + $request->getAttribute('id') + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } +} diff --git a/src/endpoints/Auth.php b/src/endpoints/Auth.php new file mode 100644 index 0000000000..fc1f3a6dad --- /dev/null +++ b/src/endpoints/Auth.php @@ -0,0 +1,268 @@ +post('/authenticate', [$this, 'authenticate']); + $app->post('/password/request', [$this, 'forgotPassword']); + // $app->get('/invitation/{token}', [$this, 'acceptInvitation']); + $app->get('/password/reset/{token}', [$this, 'resetPassword']); + $app->post('/refresh', [$this, 'refresh']); + $app->get('/sso', [$this, 'listSsoAuthServices']); + $app->get('/sso/{service}', [$this, 'ssoService']); + $app->post('/sso/{service}', [$this, 'ssoAuthenticate']); + $app->get('/sso/{service}/callback', [$this, 'ssoServiceCallback']); + $app->post('/sso/access_token', [$this, 'ssoAccessToken']); + } + + /** + * Sign In a new user, creating a new token + * + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function authenticate(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + $responseData = $authService->loginWithCredentials( + $request->getParsedBodyParam('email'), + $request->getParsedBodyParam('password') + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * Sends a user a token to reset its password + * + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function forgotPassword(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + try { + $authService->sendResetPasswordToken( + $request->getParsedBodyParam('email') + ); + } catch (\Exception $e) { + $this->container->get('logger')->error($exception); + } + + $responseData = []; + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function resetPassword(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + $authService->resetPasswordWithToken( + $request->getAttribute('token') + ); + + $responseData = []; + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * Refresh valid JWT token + * + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function refresh(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + $responseData = $authService->refreshToken( + $request->getParsedBodyParam('token') + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function listSsoAuthServices(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + /** @var Social $externalAuth */ + $externalAuth = $this->container->get('external_auth'); + + $services = []; + foreach ($externalAuth->getAll() as $name => $provider) { + $services[] = $authService->getSsoBasicInfo($name); + } + + $responseData = ['data' => $services]; + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function ssoService(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + $origin = $request->getReferer(); + $config = $this->container->get('config'); + $corsOptions = $config->get('cors', []); + $allowedOrigins = ArrayUtils::get($corsOptions, 'origin'); + $session = $this->container->get('session'); + + $responseData = $authService->getAuthenticationRequestInfo( + $request->getAttribute('service') + ); + + if (cors_is_origin_allowed($allowedOrigins, $origin)) { + if (is_array($origin)) { + $origin = array_shift($origin); + } + + $session->set('sso_origin_url', $origin); + $response = $response->withRedirect(array_get($responseData, 'data.authorization_url')); + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function ssoAuthenticate(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + $responseData = $authService->authenticateWithSsoCode( + $request->getAttribute('service'), + $request->getParsedBody() ?: [] + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws \Exception + */ + public function ssoServiceCallback(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + $session = $this->container->get('session'); + // TODO: Implement a pull method + $redirectUrl = $session->get('sso_origin_url'); + $session->remove('sso_origin_url'); + + $responseData = []; + $urlParams = []; + try { + $responseData = $authService->handleAuthenticationRequestCallback( + $request->getAttribute('service'), + !!$redirectUrl + ); + + $urlParams['request_token'] = array_get($responseData, 'data.token'); + } catch (\Exception $e) { + if (!$redirectUrl) { + throw $e; + } + + if ($e instanceof UserWithEmailNotFoundException) { + $urlParams['attributes'] = $e->getAttributes(); + } + + $urlParams['code'] = $e->getErrorCode(); + $urlParams['error'] = true; + } + + if ($redirectUrl) { + $redirectQueryString = parse_url($redirectUrl, PHP_URL_QUERY); + $redirectUrlParts = explode('?', $redirectUrl); + $redirectUrl = $redirectUrlParts[0]; + $redirectQueryParams = parse_str($redirectQueryString); + if (is_array($redirectQueryParams)) { + $urlParams = array_merge($redirectQueryParams, $urlParams); + } + + $response = $response->withRedirect($redirectUrl . '?' . http_build_query($urlParams)); + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function ssoAccessToken(Request $request, Response $response) + { + /** @var AuthService $authService */ + $authService = $this->container->get('services')->get('auth'); + + $responseData = $authService->authenticateWithSsoRequestToken( + $request->getParsedBodyParam('request_token') + ); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/CollectionPresets.php b/src/endpoints/CollectionPresets.php new file mode 100644 index 0000000000..d81112a358 --- /dev/null +++ b/src/endpoints/CollectionPresets.php @@ -0,0 +1,117 @@ +get('', [$this, 'all']); + $app->post('', [$this, 'create']); + $app->get('/{id}', [$this, 'read']); + $app->patch('/{id}', [$this, 'update']); + $app->delete('/{id}', [$this, 'delete']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new CollectionPresetsService($this->container); + + $responseData = $service->findAll($request->getQueryParams()); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new CollectionPresetsService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + ArrayUtils::pick($request->getQueryParams(), ['meta', 'fields']) + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $service = new CollectionPresetsService($this->container); + $responseData = $service->createItem( + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function update(Request $request, Response $response) + { + $service = new CollectionPresetsService($this->container); + $responseData = $service->update( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function delete(Request $request, Response $response) + { + $service = new CollectionPresetsService($this->container); + $service->delete( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } +} diff --git a/src/endpoints/Collections.php b/src/endpoints/Collections.php new file mode 100644 index 0000000000..c727bbaf78 --- /dev/null +++ b/src/endpoints/Collections.php @@ -0,0 +1,117 @@ +get('', [$this, 'all']); + $app->post('', [$this, 'create']); + $app->get('/{name}', [$this, 'read']); + $app->patch('/{name}', [$this, 'update']); + $app->delete('/{name}', [$this, 'delete']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws \Exception + */ + public function create(Request $request, Response $response) + { + $tableService = new TablesService($this->container); + $payload = $request->getParsedBody(); + $params = $request->getQueryParams(); + $name = ArrayUtils::get($payload, 'collection'); + $data = ArrayUtils::omit($payload, 'collection'); + $responseData = $tableService->createTable($name, $data, $params); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $params = $request->getQueryParams(); + $service = new TablesService($this->container); + $responseData = $service->findAll($params); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new TablesService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('name'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws UnauthorizedException + */ + public function update(Request $request, Response $response) + { + $service = new TablesService($this->container); + $responseData = $service->updateTable( + $request->getAttribute('name'), + $request->getParsedBody() ?: [], + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function delete(Request $request, Response $response) + { + $service = new TablesService($this->container); + $service->delete( + $request->getAttribute('name'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } +} diff --git a/src/endpoints/Fields.php b/src/endpoints/Fields.php new file mode 100644 index 0000000000..eb13a85424 --- /dev/null +++ b/src/endpoints/Fields.php @@ -0,0 +1,199 @@ +post('/{collection}', [$this, 'create']); + $app->get('/{collection}/{field}', [$this, 'read']); + $app->patch('/{collection}/{field}', [$this, 'update']); + $app->patch('/{collection}', [$this, 'update']); + $app->delete('/{collection}/{field}', [$this, 'delete']); + $app->get('/{collection}', [$this, 'all']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws UnauthorizedException + */ + public function create(Request $request, Response $response) + { + $service = new TablesService($this->container); + $payload = $request->getParsedBody(); + $field = ArrayUtils::pull($payload, 'field'); + + $responseData = $service->addColumn( + $request->getAttribute('collection'), + $field, + $payload, + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws FieldNotFoundException + * @throws CollectionNotFoundException + * @throws UnauthorizedException + */ + public function read(Request $request, Response $response) + { + $collectionName = $request->getAttribute('collection'); + $fieldName = $request->getAttribute('field'); + $fieldsName = StringUtils::csv((string) $fieldName); + + $service = new TablesService($this->container); + if (count($fieldsName) > 1) { + $responseData = $service->findFields($collectionName, $fieldsName, $request->getQueryParams()); + } else { + $responseData = $service->findField($collectionName, $fieldName, $request->getQueryParams()); + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws UnauthorizedException + */ + public function update(Request $request, Response $response) + { + $service = new TablesService($this->container); + $field = $request->getAttribute('field'); + $payload = $request->getParsedBody(); + + if ( + (isset($payload[0]) && is_array($payload[0])) + || strpos($field, ',') > 0 + ) { + return $this->batch($request, $response); + } + + $responseData = $service->changeColumn( + $request->getAttribute('collection'), + $request->getAttribute('field'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * Get all columns that belong to a given table + * + * NOTE: Maybe this should be on /tables instead + * + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws CollectionNotFoundException + * @throws UnauthorizedException + */ + public function all(Request $request, Response $response) + { + $service = new TablesService($this->container); + $responseData = $service->findAllFields( + $request->getAttribute('collection'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws ErrorException + * @throws UnauthorizedException + */ + public function delete(Request $request, Response $response) + { + $service = new TablesService($this->container); + + $service->deleteField( + $request->getAttribute('collection'), + $request->getAttribute('field'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws \Exception + */ + protected function batch(Request $request, Response $response) + { + $tablesService = new TablesService($this->container); + + $collection = $request->getAttribute('collection'); + $tablesService->throwErrorIfSystemTable($collection); + + $payload = $request->getParsedBody(); + $params = $request->getQueryParams(); + + if ($fields = $request->getAttribute('field')) { + $ids = explode(',', $fields); + $responseData = $tablesService->batchUpdateFieldWithIds($collection, $ids, $payload, $params); + } else { + $responseData = $tablesService->batchUpdateField($collection, $payload, $params); + } + + if (empty($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Files.php b/src/endpoints/Files.php new file mode 100644 index 0000000000..a253d4c800 --- /dev/null +++ b/src/endpoints/Files.php @@ -0,0 +1,291 @@ +post('', [$this, 'create']); + $app->get('/{id:[0-9]+}', [$this, 'read']); + $app->patch('/{id:[0-9]+}', [$this, 'update']); + $app->delete('/{id:[0-9]+}', [$this, 'delete']); + $app->get('', [$this, 'all']); + + // Folders + $controller = $this; + $app->group('/folders', function () use ($controller) { + $this->post('', [$controller, 'createFolder']); + $this->get('/{id:[0-9]+}', [$controller, 'readFolder']); + $this->patch('/{id:[0-9]+}', [$controller, 'updateFolder']); + $this->delete('/{id:[0-9]+}', [$controller, 'deleteFolder']); + $this->get('', [$controller, 'allFolder']); + }); + + // Revisions + $app->get('/{id}/revisions', [$this, 'fileRevisions']); + $app->get('/{id}/revisions/{offset}', [$this, 'oneFileRevision']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws Exception + */ + public function create(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $uploadedFiles = $request->getUploadedFiles(); + $payload = $request->getParsedBody(); + + if (!empty($uploadedFiles)) { + /** @var UploadedFile $uploadedFile */ + $uploadedFile = array_shift($uploadedFiles); + if (!is_uploaded_file_okay($uploadedFile->getError())) { + throw new FailedUploadException($uploadedFile->getError()); + } + + if (empty($payload)) { + $payload = []; + } + + // TODO: the file already exists move it to the upload path location + $data = file_get_contents($uploadedFile->file); + $payload = array_merge([ + 'filename' => $uploadedFile->getClientFilename(), + 'type' => $uploadedFile->getClientMediaType(), + 'data' => base64_encode($data) + ], $payload); + } + + $responseData = $service->create( + $payload, + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + ArrayUtils::pick($request->getParams(), ['fields', 'meta']) + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function update(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->update( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + public function delete(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $service->delete( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->findAll($request->getQueryParams()); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function createFolder(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->createFolder( + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function readFolder(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->findFolderByIds( + $request->getAttribute('id'), + ArrayUtils::pick($request->getQueryParams(), ['fields', 'meta']) + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function updateFolder(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->updateFolder( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function allFolder(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $responseData = $service->findAllFolders( + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function deleteFolder(Request $request, Response $response) + { + $service = new FilesServices($this->container); + $service->deleteFolder( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function fileRevisions(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findAllByItem( + SchemaManager::COLLECTION_FILES, + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function oneFileRevision(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findOneByItemOffset( + SchemaManager::COLLECTION_FILES, + $request->getAttribute('id'), + $request->getAttribute('offset'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function fileRevert(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->revert( + SchemaManager::COLLECTION_FILES, + $request->getAttribute('id'), + $request->getAttribute('revision'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Home.php b/src/endpoints/Home.php new file mode 100644 index 0000000000..0dd0db4d61 --- /dev/null +++ b/src/endpoints/Home.php @@ -0,0 +1,19 @@ +container); + $responseData = $service->findAllInfo(); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Interfaces.php b/src/endpoints/Interfaces.php new file mode 100644 index 0000000000..b304051bc7 --- /dev/null +++ b/src/endpoints/Interfaces.php @@ -0,0 +1,30 @@ +get('', [$this, 'all']); + } + + public function all(Request $request, Response $response) + { + $service = new InterfacesService($this->container); + $responseData = $service->findAll(); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Items.php b/src/endpoints/Items.php new file mode 100644 index 0000000000..2645038b84 --- /dev/null +++ b/src/endpoints/Items.php @@ -0,0 +1,264 @@ +get('/{collection}', [$this, 'all']); + $app->post('/{collection}', [$this, 'create']); + $app->patch('/{collection}', [$this, 'update']); + $app->get('/{collection}/{id}', [$this, 'read']); + $app->patch('/{collection}/{id}', [$this, 'update']); + $app->delete('/{collection}/{id}', [$this, 'delete']); + + // Revisions + $app->get('/{collection}/{id}/revisions', [$this, 'itemRevisions']); + $app->get('/{collection}/{id}/revisions/{offset}', [$this, 'oneItemRevision']); + $app->patch('/{collection}/{id}/revert/{revision}', [$this, 'itemRevert']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $itemsService = new ItemsService($this->container); + + $collection = $request->getAttribute('collection'); + $itemsService->throwErrorIfSystemTable($collection); + + $params = $request->getQueryParams(); + $responseData = $itemsService->findAll($collection, $params); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $payload = $request->getParsedBody(); + if (isset($payload[0]) && is_array($payload[0])) { + return $this->batch($request, $response); + } + + $itemsService = new ItemsService($this->container); + $collection = $request->getAttribute('collection'); + $itemsService->throwErrorIfSystemTable($collection); + + $responseData = $itemsService->createItem( + $collection, + $payload, + $request->getQueryParams() + ); + + if (is_null($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws BadRequestException + */ + public function read(Request $request, Response $response) + { + $itemsService = new ItemsService($this->container); + $collection = $request->getAttribute('collection'); + $itemsService->throwErrorIfSystemTable($collection); + + $responseData = $itemsService->findByIds( + $collection, + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws BadRequestException + */ + public function update(Request $request, Response $response) + { + $itemsService = new ItemsService($this->container); + $collection = $request->getAttribute('collection'); + $itemsService->throwErrorIfSystemTable($collection); + + $payload = $request->getParsedBody(); + if (isset($payload[0]) && is_array($payload[0])) { + return $this->batch($request, $response); + } + + $id = $request->getAttribute('id'); + + if (strpos($id, ',') !== false) { + return $this->batch($request, $response); + } + + $params = $request->getQueryParams(); + $responseData = $itemsService->update($collection, $id, $payload, $params); + + if (is_null($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws BadRequestException + */ + public function delete(Request $request, Response $response) + { + $itemsService = new ItemsService($this->container); + $collection = $request->getAttribute('collection'); + $itemsService->throwErrorIfSystemTable($collection); + + $id = $request->getAttribute('id'); + if (strpos($id, ',') !== false) { + return $this->batch($request, $response); + } + + $itemsService->delete($collection, $request->getAttribute('id'), $request->getQueryParams()); + + $responseData = []; + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function itemRevisions(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findAllByItem( + $request->getAttribute('collection'), + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function oneItemRevision(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findOneByItemOffset( + $request->getAttribute('collection'), + $request->getAttribute('id'), + $request->getAttribute('offset'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function itemRevert(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->revert( + $request->getAttribute('collection'), + $request->getAttribute('id'), + $request->getAttribute('revision'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws \Exception + */ + protected function batch(Request $request, Response $response) + { + $itemsService = new ItemsService($this->container); + + $collection = $request->getAttribute('collection'); + $itemsService->throwErrorIfSystemTable($collection); + + $payload = $request->getParsedBody(); + $params = $request->getQueryParams(); + + $responseData = null; + if ($request->isPost()) { + $responseData = $itemsService->batchCreate($collection, $payload, $params); + } else if ($request->isPatch()) { + if ($request->getAttribute('id')) { + $ids = explode(',', $request->getAttribute('id')); + $responseData = $itemsService->batchUpdateWithIds($collection, $ids, $payload, $params); + } else { + $responseData = $itemsService->batchUpdate($collection, $payload, $params); + } + } else if ($request->isDelete()) { + $ids = explode(',', $request->getAttribute('id')); + $itemsService->batchDeleteWithIds($collection, $ids, $params); + } + + if (empty($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Listings.php b/src/endpoints/Listings.php new file mode 100644 index 0000000000..0d67129760 --- /dev/null +++ b/src/endpoints/Listings.php @@ -0,0 +1,30 @@ +get('', [$this, 'all']); + } + + public function all(Request $request, Response $response) + { + $service = new ListingsService($this->container); + $responseData = $service->findAll(); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Pages.php b/src/endpoints/Pages.php new file mode 100644 index 0000000000..08048eca96 --- /dev/null +++ b/src/endpoints/Pages.php @@ -0,0 +1,30 @@ +get('', [$this, 'all']); + } + + public function all(Request $request, Response $response) + { + $service = new PagesService($this->container); + $responseData = $service->findAll(); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Permissions.php b/src/endpoints/Permissions.php new file mode 100644 index 0000000000..ea9d2696d6 --- /dev/null +++ b/src/endpoints/Permissions.php @@ -0,0 +1,108 @@ +post('', [$this, 'create']); + $app->get('/{id}', [$this, 'read']); + $app->patch('/{id}', [$this, 'update']); + $app->delete('/{id}', [$this, 'delete']); + $app->get('', [$this, 'all']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $service = new PermissionsService($this->container); + $responseData = $service->create( + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new PermissionsService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + ArrayUtils::pick($request->getQueryParams(), ['fields', 'meta']) + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function update(Request $request, Response $response) + { + $service = new PermissionsService($this->container); + $responseData = $service->update( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function delete(Request $request, Response $response) + { + $service = new PermissionsService($this->container); + $service->delete( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new PermissionsService($this->container); + $responseData = $service->findAll( + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Relations.php b/src/endpoints/Relations.php new file mode 100644 index 0000000000..4f5330a199 --- /dev/null +++ b/src/endpoints/Relations.php @@ -0,0 +1,171 @@ +get('', [$this, 'all']); + $app->post('', [$this, 'create']); + $app->get('/{id}', [$this, 'read']); + $app->patch('/{id}', [$this, 'update']); + $app->delete('/{id}', [$this, 'delete']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $params = $request->getQueryParams(); + + $relationsService = new RelationsService($this->container); + $responseData = $relationsService->findAll($params); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $payload = $request->getParsedBody(); + if (isset($payload[0]) && is_array($payload[0])) { + return $this->batch($request, $response); + } + + $relationsService = new RelationsService($this->container); + $responseData = $relationsService->create( + $payload, + $request->getQueryParams() + ); + + if (is_null($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws BadRequestException + */ + public function read(Request $request, Response $response) + { + $relationsService = new RelationsService($this->container); + $responseData = $relationsService->findByIds( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws BadRequestException + */ + public function update(Request $request, Response $response) + { + $id = $request->getAttribute('id'); + + if (strpos($id, ',') !== false) { + return $this->batch($request, $response); + } + + $payload = $request->getParsedBody(); + $params = $request->getQueryParams(); + + $relationsService = new RelationsService($this->container); + $responseData = $relationsService->update($id, $payload, $params); + + if (is_null($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws BadRequestException + */ + public function delete(Request $request, Response $response) + { + $id = $request->getAttribute('id'); + if (strpos($id, ',') !== false) { + return $this->batch($request, $response); + } + + $relationsService = new RelationsService($this->container); + $relationsService->delete($request->getAttribute('id'), $request->getQueryParams()); + + $responseData = []; + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + * + * @throws \Exception + */ + protected function batch(Request $request, Response $response) + { + $payload = $request->getParsedBody(); + $params = $request->getQueryParams(); + + $responseData = null; + $relationsService = new RelationsService($this->container); + if ($request->isPost()) { + $responseData = $relationsService->batchCreate($payload, $params); + } else if ($request->isPatch()) { + $ids = explode(',', $request->getAttribute('id')); + $responseData = $relationsService->batchUpdateWithIds($ids, $payload, $params); + } else if ($request->isDelete()) { + $ids = explode(',', $request->getAttribute('id')); + $relationsService->batchDeleteWithIds($ids, $params); + } + + if (empty($responseData)) { + $response = $response->withStatus(204); + $responseData = []; + } + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Revisions.php b/src/endpoints/Revisions.php new file mode 100644 index 0000000000..a27d54c927 --- /dev/null +++ b/src/endpoints/Revisions.php @@ -0,0 +1,54 @@ +get('', [$this, 'all']); + $app->get('/{id}', [$this, 'read']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findAll( + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Roles.php b/src/endpoints/Roles.php new file mode 100644 index 0000000000..6a4ea6ebf2 --- /dev/null +++ b/src/endpoints/Roles.php @@ -0,0 +1,110 @@ +post('', [$this, 'create']); + $app->get('/{id}', [$this, 'read']); + $app->patch('/{id}', [$this, 'update']); + $app->delete('/{id}', [$this, 'delete']); + $app->get('', [$this, 'all']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $service = new RolesService($this->container); + $responseData = $service->create( + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new RolesService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + ArrayUtils::pick($request->getQueryParams(), ['fields', 'meta']) + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function update(Request $request, Response $response) + { + $service = new RolesService($this->container); + $responseData = $service->update( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new RolesService($this->container); + $responseData = $service->findAll($request->getQueryParams()); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function delete(Request $request, Response $response) + { + $service = new RolesService($this->container); + $service->delete( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } +} diff --git a/src/endpoints/ScimTwo.php b/src/endpoints/ScimTwo.php new file mode 100644 index 0000000000..e2008cfe6d --- /dev/null +++ b/src/endpoints/ScimTwo.php @@ -0,0 +1,230 @@ +post('/Users', [$this, 'createUser'])->setName('scim_v2_create_user'); + $app->get('/Users/{id}', [$this, 'oneUser'])->setName('scim_v2_read_user'); + $app->map(['PUT', 'PATCH'], '/Users/{id}', [$this, 'updateUser'])->setName('scim_v2_update_user'); + $app->get('/Users', [$this, 'listUsers'])->setName('scim_v2_list_users'); + + // Groups + $app->post('/Groups', [$this, 'createGroup'])->setName('scim_v2_create_group'); + $app->get('/Groups', [$this, 'listGroups'])->setName('scim_v2_list_groups'); + $app->get('/Groups/{id}', [$this, 'oneGroup'])->setName('scim_v2_read_group'); + $app->map(['PUT', 'PATCH'], '/Groups/{id}', [$this, 'updateGroup'])->setName('scim_v2_update_group'); + $app->delete('/Groups/{id}', [$this, 'deleteGroup'])->setName('scim_v2_delete_group'); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function createUser(Request $request, Response $response) + { + $service = $this->getService(); + $response->setHeader('Content-Type', 'application/scim+json'); + + $responseData = $service->createUser( + $request->getParsedBody() + ); + + $response = $response->withStatus(201); + + return $this->responseScimWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function createGroup(Request $request, Response $response) + { + $service = $this->getService(); + $response->setHeader('Content-Type', 'application/scim+json'); + + $responseData = $service->createGroup( + $request->getParsedBody() + ); + + $response = $response->withStatus(201); + + return $this->responseScimWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function updateUser(Request $request, Response $response) + { + $service = $this->getService(); + $response->setHeader('Content-Type', 'application/scim+json'); + + $responseData = $service->updateUser( + $request->getAttribute('id'), + $request->getParsedBody() + ); + + return $this->responseScimWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function updateGroup(Request $request, Response $response) + { + $service = $this->getService(); + $response->setHeader('Content-Type', 'application/scim+json'); + + $responseData = $service->updateGroup( + $request->getAttribute('id'), + $request->getParsedBody() + ); + + return $this->responseScimWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function oneUser(Request $request, Response $response) + { + $service = $this->getService(); + $response->setHeader('Content-Type', 'application/scim+json'); + + $responseData = $service->findUser( + $request->getAttribute('id') + ); + + return $this->responseScimWithData( + $request, + $response, + $responseData + ); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function oneGroup(Request $request, Response $response) + { + $service = $this->getService(); + $response->setHeader('Content-Type', 'application/scim+json'); + + $responseData = $service->findGroup( + $request->getAttribute('id') + ); + + return $this->responseScimWithData( + $request, + $response, + $responseData + ); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function listUsers(Request $request, Response $response) + { + $response->setHeader('Content-Type', 'application/scim+json'); + $responseData = $this->getService()->findAllUsers($request->getQueryParams()); + + return $this->responseScimWithData( + $request, + $response, + $responseData + ); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function listGroups(Request $request, Response $response) + { + $response->setHeader('Content-Type', 'application/scim+json'); + $responseData = $this->getService()->findAllGroups($request->getQueryParams()); + + return $this->responseScimWithData( + $request, + $response, + $responseData + ); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function deleteGroup(Request $request, Response $response) + { + $response->setHeader('Content-Type', 'application/scim+json'); + $this->getService()->deleteGroup($request->getAttribute('id')); + + $response = $response->withStatus(204); + + return $this->responseScimWithData( + $request, + $response, + [] + ); + } + + /** + * Gets the users service + * + * @return ScimService + */ + protected function getService() + { + if (!$this->service) { + $this->service = new ScimService($this->container); + } + + return $this->service; + } +} diff --git a/src/endpoints/Server.php b/src/endpoints/Server.php new file mode 100644 index 0000000000..d0ec998c72 --- /dev/null +++ b/src/endpoints/Server.php @@ -0,0 +1,17 @@ +post('', [$this, 'create']); + $app->get('', [$this, 'all']); + $app->get('/{id}', [$this, 'read']); + $app->patch('/{id}', [$this, 'update']); + $app->delete('/{id}', [$this, 'delete']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $service = new SettingsService($this->container); + $responseData = $service->create( + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new SettingsService($this->container); + $responseData = $service->findAll( + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new SettingsService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function update(Request $request, Response $response) + { + $service = new SettingsService($this->container); + $responseData = $service->update( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function delete(Request $request, Response $response) + { + $service = new SettingsService($this->container); + $service->delete( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } +} diff --git a/src/endpoints/Types.php b/src/endpoints/Types.php new file mode 100644 index 0000000000..7aeca004b8 --- /dev/null +++ b/src/endpoints/Types.php @@ -0,0 +1,37 @@ +get('', [$this, 'all']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $responseData = [ + 'data' => array_map(function ($type) { + return ['name' => $type]; + }, DataTypes::getAllTypes()) + ]; + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Users.php b/src/endpoints/Users.php new file mode 100644 index 0000000000..b1773b75c1 --- /dev/null +++ b/src/endpoints/Users.php @@ -0,0 +1,201 @@ +get('', [$this, 'all']); + $app->post('', [$this, 'create']); + $app->get('/{id}', [$this, 'read']); + $app->post('/invite', [$this, 'invite']); + $app->patch('/{id}', [$this, 'update']); + $app->delete('/{id}', [$this, 'delete']); + + // Revisions + $app->get('/{id}/revisions', [$this, 'userRevisions']); + $app->get('/{id}/revisions/{offset}', [$this, 'oneUserRevision']); + + // Tracking + $app->patch('/{id}/tracking/page', [$this, 'trackPage']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function all(Request $request, Response $response) + { + $service = new UsersService($this->container); + $responseData = $service->findAll( + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function create(Request $request, Response $response) + { + $service = new UsersService($this->container); + $responseData = $service->create( + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function read(Request $request, Response $response) + { + $service = new UsersService($this->container); + $responseData = $service->findByIds( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function invite(Request $request, Response $response) + { + $service = new UsersService($this->container); + + $email = $request->getParsedBodyParam('email'); + $emails = explode(',', $email); + + $responseData = $service->invite( + $emails, + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function update(Request $request, Response $response) + { + $service = new UsersService($this->container); + $responseData = $service->update( + $request->getAttribute('id'), + $request->getParsedBody(), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function delete(Request $request, Response $response) + { + $service = new UsersService($this->container); + $service->delete( + $request->getAttribute('id'), + $request->getQueryParams() + ); + + $response = $response->withStatus(204); + + return $this->responseWithData($request, $response, []); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function userRevisions(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findAllByItem( + SchemaManager::COLLECTION_USERS, + $request->getAttribute('id'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function oneUserRevision(Request $request, Response $response) + { + $service = new RevisionsService($this->container); + $responseData = $service->findOneByItemOffset( + SchemaManager::COLLECTION_USERS, + $request->getAttribute('id'), + $request->getAttribute('offset'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function trackPage(Request $request, Response $response) + { + $service = new UsersService($this->container); + $responseData = $service->updateLastPage( + $request->getAttribute('id'), + $request->getParsedBodyParam('last_page'), + $request->getQueryParams() + ); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/endpoints/Utils.php b/src/endpoints/Utils.php new file mode 100644 index 0000000000..28f1f8a874 --- /dev/null +++ b/src/endpoints/Utils.php @@ -0,0 +1,88 @@ +post('/hash', [$this, 'hash']); + $app->post('/hash/match', [$this, 'matchHash']); + $app->post('/random/string', [$this, 'randomString']); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function hash(Request $request, Response $response) + { + $service = new UtilsService($this->container); + + $options = $request->getParam('options', []); + if (!is_array($options)) { + $options = [$options]; + } + + $responseData = $service->hashString( + $request->getParam('string'), + $request->getParam('hasher', 'core'), + $options + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function matchHash(Request $request, Response $response) + { + $service = new UtilsService($this->container); + + $options = $request->getParam('options', []); + if (!is_array($options)) { + $options = [$options]; + } + + $responseData = $service->verifyHashString( + $request->getParam('string'), + $request->getParam('hash'), + $request->getParam('hasher', 'core'), + $options + ); + + return $this->responseWithData($request, $response, $responseData); + } + + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function randomString(Request $request, Response $response) + { + $service = new UtilsService($this->container); + $responseData = $service->randomString( + $request->getParsedBodyParam('length', 32), + $request->getParsedBodyParam('options') + ); + + return $this->responseWithData($request, $response, $responseData); + } +} diff --git a/src/helpers/all.php b/src/helpers/all.php new file mode 100644 index 0000000000..0bc5110bcb --- /dev/null +++ b/src/helpers/all.php @@ -0,0 +1,1574 @@ + 'IDs', + 'Ssn' => 'SSN', + 'Ein' => 'EIN', + 'Nda' => 'NDA', + 'Api' => 'API', + 'Youtube' => 'YouTube', + 'Faq' => 'FAQ', + 'Iphone' => 'iPhone', + 'Ipad' => 'iPad', + 'Ipod' => 'iPod', + 'Pdf' => 'PDF', + 'Pdfs' => 'PDFs', + 'Ui' => 'UI', + 'Url' => 'URL', + 'Urls' => 'URLs', + 'Ip' => 'IP', + 'Ftp' => 'FTP', + 'Db' => 'DB', + 'Cv' => 'CV', + 'Id' => 'ID', + 'Ph' => 'pH', + 'Php' => 'PHP', + 'Html' => 'HTML', + 'Js' => 'JS', + 'Json' => 'JSON', + 'Css' => 'CSS', + 'Csv' => 'CSV', + 'Ios' => 'iOS', + 'Iso' => 'ISO', + 'Rngr' => 'RNGR' + ]; + + $searchPattern = array_keys($specialCaps); + $replaceValues = array_values($specialCaps); + foreach ($searchPattern as $key => $value) { + $searchPattern[$key] = ("/\b" . $value . "\b/"); + } + + return preg_replace($searchPattern, $replaceValues, $phrase); + } +} + +if (!function_exists('get_directus_path')) { + /** + * Gets the Directus path (subdirectory based on the host) + * + * @param string $subPath + * + * @return string + */ + function get_directus_path($subPath = '') + { + $path = create_uri_from_global()->getBasePath(); + + $path = trim($path, '/'); + $subPath = ltrim($subPath, '/'); + + return (empty($path) ? '/' : sprintf('/%s/', $path)) . $subPath; + } +} + +if (!function_exists('normalize_path')) { + /** + * Normalize a filesystem path. + * + * On windows systems, replaces backslashes with forward slashes. + * Ensures that no duplicate slashes exist. + * + * from WordPress source code + * + * @param string $path Path to normalize. + * + * @return string Normalized path. + */ + function normalize_path($path) + { + $path = str_replace('\\', '/', $path); + $path = preg_replace('|/+|','/', $path); + + return $path; + } +} + +if (!function_exists('get_url')) { + /** + * Get Directus URL + * + * @param $path - Extra path to add to the url + * + * @return string + */ + function get_url($path = '') + { + return create_uri_from_global()->getBaseUrl() . '/' . ltrim($path, '/'); + } +} + + +if (!function_exists('create_uri_from_global')) +{ + /** + * Creates a uri object based on $_SERVER + * + * Snippet copied from Slim URI class + * + * @return \Slim\Http\Uri + */ + function create_uri_from_global() + { + // Scheme + $env = $_SERVER; + $isSecure = \Directus\Util\ArrayUtils::get($env, 'HTTPS'); + $scheme = (empty($isSecure) || $isSecure === 'off') ? 'http' : 'https'; + + // Authority: Username and password + $username = \Directus\Util\ArrayUtils::get($env, 'PHP_AUTH_USER', ''); + $password = \Directus\Util\ArrayUtils::get($env, 'PHP_AUTH_PW', ''); + + // Authority: Host + if (\Directus\Util\ArrayUtils::has($env, 'HTTP_HOST')) { + $host = \Directus\Util\ArrayUtils::get($env, 'HTTP_HOST'); + } else { + $host = \Directus\Util\ArrayUtils::get($env, 'SERVER_NAME'); + } + + // Authority: Port + $port = (int)\Directus\Util\ArrayUtils::get($env, 'SERVER_PORT', 80); + if (preg_match('/^(\[[a-fA-F0-9:.]+\])(:\d+)?\z/', $host, $matches)) { + $host = $matches[1]; + + if (isset($matches[2])) { + $port = (int)substr($matches[2], 1); + } + } else { + $pos = strpos($host, ':'); + if ($pos !== false) { + $port = (int)substr($host, $pos + 1); + $host = strstr($host, ':', true); + } + } + + // Path + $requestScriptName = parse_url(\Directus\Util\ArrayUtils::get($env, 'SCRIPT_NAME'), PHP_URL_PATH); + $requestScriptDir = dirname($requestScriptName); + + // parse_url() requires a full URL. As we don't extract the domain name or scheme, + // we use a stand-in. + $requestUri = parse_url('http://example.com' . \Directus\Util\ArrayUtils::get($env, 'REQUEST_URI'), PHP_URL_PATH); + + $basePath = ''; + $virtualPath = $requestUri; + if ($requestUri == $requestScriptName) { + $basePath = $requestScriptDir; + } elseif (stripos($requestUri, $requestScriptName) === 0) { + $basePath = $requestScriptName; + } elseif ($requestScriptDir !== '/' && stripos($requestUri, $requestScriptDir) === 0) { + $basePath = $requestScriptDir; + } + + if ($basePath) { + $virtualPath = ltrim(substr($requestUri, strlen($basePath)), '/'); + } + + // Query string + $queryString = \Directus\Util\ArrayUtils::get($env, 'QUERY_STRING', ''); + if ($queryString === '') { + $queryString = parse_url('http://example.com' . \Directus\Util\ArrayUtils::get($env, 'REQUEST_URI'), PHP_URL_QUERY); + } + + // Fragment + $fragment = ''; + + // Build Uri + $uri = new \Slim\Http\Uri($scheme, $host, $port, $virtualPath, $queryString, $fragment, $username, $password); + if ($basePath) { + $uri = $uri->withBasePath($basePath); + } + + return $uri; + } +} + +if (!function_exists('get_virtual_path')) { + /** + * Gets the virtual request path + * + * @return string + */ + function get_virtual_path() + { + return create_uri_from_global()->getPath(); + } +} + +if (!function_exists('get_api_env_from_request')) { + /** + * Gets the env from the request uri + * + * @return string + */ + function get_api_env_from_request() + { + $path = trim(get_virtual_path(), '/'); + $parts = explode('/', $path); + + return isset($parts[0]) ? $parts[0] : '_'; + } +} + +if (!function_exists('get_file_info')) { + /** + * Get info about a file, return extensive information about images (more to come) + * + * @return array File info + */ + function get_file_info($file) + { + $finfo = new finfo(FILEINFO_MIME); + $type = explode('; charset=', $finfo->file($file)); + $info = ['type' => $type[0], 'charset' => $type[1]]; + + $type_str = explode('/', $info['type']); + + if ($type_str[0] == 'image') { + $size = getimagesize($file, $meta); + $info['width'] = $size[0]; + $info['height'] = $size[1]; + + if (isset($meta['APP13'])) { + $iptc = iptcparse($meta['APP13']); + $info['caption'] = $iptc['2#120'][0]; + $info['title'] = $iptc['2#005'][0]; + $info['tags'] = implode($iptc['2#025'], ','); + } + } + + return $info; + } +} + +if (!function_exists('template')) { + /** + * Renders a single line. Looks for {{ var }} + * + * @param string $string + * @param array $parameters + * + * @return string + */ + function template($string, array $parameters) + { + $replacer = function ($match) use ($parameters) { + return isset($parameters[$match[1]]) ? $parameters[$match[1]] : $match[0]; + }; + + return preg_replace_callback('/{{\s*(.+?)\s*}}/', $replacer, $string); + } +} + +if (!function_exists('to_name_value')) { + function to_name_value($array, $keys = null) + { + $data = []; + foreach ($array as $name => $value) { + $row = ['name' => $name, 'value' => $value]; + if (isset($keys)) $row = array_merge($row, $keys); + array_push($data, $row); + } + + return $data; + } +} + +if (!function_exists('find')) { + function find($array, $key, $value) + { + foreach ($array as $item) { + if (isset($item[$key]) && ($item[$key] == $value)) return $item; + } + } +} + +if (!function_exists('is_numeric_array')) { + // http://stackoverflow.com/questions/902857/php-getting-array-type + function is_numeric_array($array) + { + return ($array == array_values($array)); + } +} + +if (!function_exists('is_numeric_keys_array')) { + function is_numeric_keys_array($array) + { + return \Directus\Util\ArrayUtils::isNumericKeys($array); + } +} + +if (!function_exists('debug')) { + function debug($data, $title = null) + { + echo '
    '; + echo "$title"; + echo '
    ';
    +        print_r($data);
    +        echo '
    '; + echo '
    '; + } +} + +if (!function_exists('register_global_hooks')) { + /** + * Register all the hooks from the configuration file + * + * @param \Directus\Application\Application $app + */ + function register_global_hooks(\Directus\Application\Application $app) + { + $config = $app->getConfig(); + register_hooks_list($app, [$config->get('hooks')]); + } +} + +if (!function_exists('register_extensions_hooks')) { + /** + * Register all extensions hooks + * + * @param \Directus\Application\Application $app + */ + function register_extensions_hooks(\Directus\Application\Application $app) + { + register_hooks_list( + $app, + get_custom_hooks('public/extensions/custom/hooks') + ); + + register_hooks_list( + $app, + get_custom_hooks('public/extensions/core/pages', true) + ); + + register_hooks_list( + $app, + get_custom_hooks('public/extensions/core/interfaces', true) + ); + } +} + +if (!function_exists('register_hooks_list')) { + /** + * Register an array of hooks (containing a list of actions and filters) + * + * @param \Directus\Application\Application $app + * @param array $hooksList + */ + function register_hooks_list(\Directus\Application\Application $app, array $hooksList) + { + foreach ($hooksList as $hooks) { + register_hooks($app, array_get($hooks, 'actions', []), false); + register_hooks($app, array_get($hooks, 'filters', []), true); + } + } +} + +if (!function_exists('register_hooks')) { + /** + * Load one or multiple listeners + * + * @param \Directus\Application\Application $app + * @param array|Closure $listeners + * @param bool $areFilters + */ + function register_hooks(\Directus\Application\Application $app, $listeners, $areFilters = false) + { + $hookEmitter = $app->getContainer()->get('hook_emitter'); + + if (!is_array($listeners)) { + $listeners = [$listeners]; + } + + foreach ($listeners as $event => $handlers) { + if (!is_array($handlers)) { + $handlers = [$handlers]; + } + + foreach ($handlers as $handler) { + register_hook($hookEmitter, $event, $handler, null, $areFilters); + } + } + } +} + +if (!function_exists('register_hook')) { + /** + * Register a hook listeners + * + * @param \Directus\Hook\Emitter $emitter + * @param string $name + * @param callable $listener + * @param int|null $priority + * @param bool $areFilters + */ + function register_hook(\Directus\Hook\Emitter $emitter, $name, $listener, $priority = null, $areFilters = false) + { + if (!$areFilters) { + register_action_hook($emitter, $name, $listener, $priority); + } else { + register_filter_hook($emitter, $name, $listener, $priority); + } + } +} + +if (!function_exists('register_action_hook')) { + /** + * Register a hook action + * + * @param \Directus\Hook\Emitter $emitter + * @param string $name + * @param callable $listener + * @param int|null $priority + */ + function register_action_hook(\Directus\Hook\Emitter $emitter, $name, $listener, $priority = null) + { + $emitter->addAction($name, $listener, $priority); + } +} + +if (!function_exists('register_hook_filter')) { + /** + * Register a hook action + * + * @param \Directus\Hook\Emitter $emitter + * @param string $name + * @param callable $listener + * @param int|null $priority + */ + function register_filter_hook(\Directus\Hook\Emitter $emitter, $name, $listener, $priority = null) + { + $emitter->addFilter($name, $listener, $priority); + } +} + +if (!function_exists('get_user_timezone')) { + function get_user_timezone() + { + $userTimeZone = get_auth_timezone(); + + if (!$userTimeZone) { + $userTimeZone = date_default_timezone_get(); + } + + return $userTimeZone; + } +} + +if (!function_exists('get_auth_info')) { + function get_auth_info($attribute) + { + $app = \Directus\Application\Application::getInstance(); + try { + /** @var \Directus\Authentication\Provider $authentication */ + $authentication = $app->getContainer()->get('auth'); + } catch (\Exception $e) { + return null; + } + + return $authentication->getUserAttributes($attribute); + } +} + +if (!function_exists('get_auth_timezone')) { + function get_auth_timezone() + { + return get_auth_info('timezone'); + } +} + +if (!function_exists('base_path')) { + function base_path($suffix = '') + { + $app = \Directus\Application\Application::getInstance(); + + $path = $app ? $app->getContainer()->get('path_base') : realpath(__DIR__ . '/../../'); + + if (!is_string($suffix)) { + throw new \Directus\Exception\Exception('suffix must be a string'); + } + + if ($suffix) { + $path = rtrim($path, '/') . '/' . ltrim($suffix, '/'); + } + + return $path; + } +} + +if (!function_exists('get_fake_timezones')) { + /** + * Gets the list of fake timezone map to an real one + * + * @return array + */ + function get_fake_timezones() + { + return [ + 'America/Mexico/La_Paz' => 'America/Chihuahua', + 'America/Guadalajara' => 'America/Mexico_City', + 'America/Quito' => 'America/Bogota', + 'America/Argentina/GeorgeTown' => 'America/Argentina/Buenos_Aires', + 'Europe/Edinburgh' => 'Europe/London', + 'Europe/Bern' => 'Europe/Berlin', + 'Europe/Kyiv' => 'Europe/Helsinki', + 'Asia/Abu_Dhabi' => 'Asia/Muscat', + 'Europe/St_Petersburg' => 'Europe/Moscow', + 'Asia/Islamabad' => 'Asia/Karachi', + 'Asia/Mumbai' => 'Asia/Calcutta', + 'Asia/New_Delhi' => 'Asia/Calcutta', + 'Asia/Sri_Jayawardenepura' => 'Asia/Calcutta', + 'Asia/Astana' => 'Asia/Dhaka', + 'Asia/Hanoi' => 'Asia/Bangkok', + 'Asia/Beijing' => 'Asia/Hong_Kong', + 'Asia/Sapporo' => 'Asia/Tokyo', + 'Asia/Osaka' => 'Asia/Tokyo', + 'Pacific/Marshall_Is' => 'Pacific/Fiji', + 'Asia/Solomon_Is' => 'Asia/Magadan', + 'Asia/New_Caledonia' => 'Asia/Magadan', + 'Pacific/Wellington' => 'Pacific/Auckland' + ]; + } +} + +if (!function_exists('get_real_timezone')) { + /** + * Gets the real name of the timezone name + * + * we have fake it until php makes it + * + * @param $name + * + * @return string + */ + function get_real_timezone($name) + { + $fakes = get_fake_timezones(); + return isset($fakes[$name]) ? $fakes[$name] : $name; + } +} + +if (!function_exists('get_timezone_list')) { + /** + * @return array + */ + function get_timezone_list() + { + // List from: https://github.com/tamaspap/timezones + return [ + 'Pacific/Midway' => '(UTC-11:00) Midway Island', + 'Pacific/Samoa' => '(UTC-11:00) Samoa', + 'Pacific/Honolulu' => '(UTC-10:00) Hawaii', + 'US/Alaska' => '(UTC-09:00) Alaska', + 'America/Los_Angeles' => '(UTC-08:00) Pacific Time (US & Canada)', + 'America/Tijuana' => '(UTC-08:00) Tijuana', + 'US/Arizona' => '(UTC-07:00) Arizona', + 'America/Chihuahua' => '(UTC-07:00) Chihuahua', + 'America/Mexico/La_Paz' => '(UTC-07:00) La Paz', + 'America/Mazatlan' => '(UTC-07:00) Mazatlan', + 'US/Mountain' => '(UTC-07:00) Mountain Time (US & Canada)', + 'America/Managua' => '(UTC-06:00) Central America', + 'US/Central' => '(UTC-06:00) Central Time (US & Canada)', + 'America/Guadalajara' => '(UTC-06:00) Guadalajara', + 'America/Mexico_City' => '(UTC-06:00) Mexico City', + 'America/Monterrey' => '(UTC-06:00) Monterrey', + 'Canada/Saskatchewan' => '(UTC-06:00) Saskatchewan', + 'America/Bogota' => '(UTC-05:00) Bogota', + 'US/Eastern' => '(UTC-05:00) Eastern Time (US & Canada)', + 'US/East-Indiana' => '(UTC-05:00) Indiana (East)', + 'America/Lima' => '(UTC-05:00) Lima', + 'America/Quito' => '(UTC-05:00) Quito', + 'Canada/Atlantic' => '(UTC-04:00) Atlantic Time (Canada)', + 'America/New_York' => '(UTC-04:00) New York', + 'America/Caracas' => '(UTC-04:30) Caracas', + 'America/La_Paz' => '(UTC-04:00) La Paz', + 'America/Santiago' => '(UTC-04:00) Santiago', + 'America/Santo_Domingo' => '(UTC-04:00) Santo Domingo', + 'Canada/Newfoundland' => '(UTC-03:30) Newfoundland', + 'America/Sao_Paulo' => '(UTC-03:00) Brasilia', + 'America/Argentina/Buenos_Aires' => '(UTC-03:00) Buenos Aires', + 'America/Argentina/GeorgeTown' => '(UTC-03:00) Georgetown', + 'America/Godthab' => '(UTC-03:00) Greenland', + 'America/Noronha' => '(UTC-02:00) Mid-Atlantic', + 'Atlantic/Azores' => '(UTC-01:00) Azores', + 'Atlantic/Cape_Verde' => '(UTC-01:00) Cape Verde Is.', + 'Africa/Casablanca' => '(UTC+00:00) Casablanca', + 'Europe/Edinburgh' => '(UTC+00:00) Edinburgh', + 'Etc/Greenwich' => '(UTC+00:00) Greenwich Mean Time : Dublin', + 'Europe/Lisbon' => '(UTC+00:00) Lisbon', + 'Europe/London' => '(UTC+00:00) London', + 'Africa/Monrovia' => '(UTC+00:00) Monrovia', + 'UTC' => '(UTC+00:00) UTC', + 'Europe/Amsterdam' => '(UTC+01:00) Amsterdam', + 'Europe/Belgrade' => '(UTC+01:00) Belgrade', + 'Europe/Berlin' => '(UTC+01:00) Berlin', + 'Europe/Bern' => '(UTC+01:00) Bern', + 'Europe/Bratislava' => '(UTC+01:00) Bratislava', + 'Europe/Brussels' => '(UTC+01:00) Brussels', + 'Europe/Budapest' => '(UTC+01:00) Budapest', + 'Europe/Copenhagen' => '(UTC+01:00) Copenhagen', + 'Europe/Ljubljana' => '(UTC+01:00) Ljubljana', + 'Europe/Madrid' => '(UTC+01:00) Madrid', + 'Europe/Paris' => '(UTC+01:00) Paris', + 'Europe/Prague' => '(UTC+01:00) Prague', + 'Europe/Rome' => '(UTC+01:00) Rome', + 'Europe/Sarajevo' => '(UTC+01:00) Sarajevo', + 'Europe/Skopje' => '(UTC+01:00) Skopje', + 'Europe/Stockholm' => '(UTC+01:00) Stockholm', + 'Europe/Vienna' => '(UTC+01:00) Vienna', + 'Europe/Warsaw' => '(UTC+01:00) Warsaw', + 'Africa/Lagos' => '(UTC+01:00) West Central Africa', + 'Europe/Zagreb' => '(UTC+01:00) Zagreb', + 'Europe/Athens' => '(UTC+02:00) Athens', + 'Europe/Bucharest' => '(UTC+02:00) Bucharest', + 'Africa/Cairo' => '(UTC+02:00) Cairo', + 'Africa/Harare' => '(UTC+02:00) Harare', + 'Europe/Helsinki' => '(UTC+02:00) Helsinki', + 'Europe/Istanbul' => '(UTC+02:00) Istanbul', + 'Asia/Jerusalem' => '(UTC+02:00) Jerusalem', + 'Europe/Kyiv' => '(UTC+02:00) Kyiv', + 'Africa/Johannesburg' => '(UTC+02:00) Pretoria', + 'Europe/Riga' => '(UTC+02:00) Riga', + 'Europe/Sofia' => '(UTC+02:00) Sofia', + 'Europe/Tallinn' => '(UTC+02:00) Tallinn', + 'Europe/Vilnius' => '(UTC+02:00) Vilnius', + 'Asia/Baghdad' => '(UTC+03:00) Baghdad', + 'Asia/Kuwait' => '(UTC+03:00) Kuwait', + 'Europe/Minsk' => '(UTC+03:00) Minsk', + 'Africa/Nairobi' => '(UTC+03:00) Nairobi', + 'Asia/Riyadh' => '(UTC+03:00) Riyadh', + 'Europe/Volgograd' => '(UTC+03:00) Volgograd', + 'Asia/Tehran' => '(UTC+03:30) Tehran', + 'Asia/Abu_Dhabi' => '(UTC+04:00) Abu Dhabi', + 'Asia/Baku' => '(UTC+04:00) Baku', + 'Europe/Moscow' => '(UTC+04:00) Moscow', + 'Asia/Muscat' => '(UTC+04:00) Muscat', + 'Europe/St_Petersburg' => '(UTC+04:00) St. Petersburg', + 'Asia/Tbilisi' => '(UTC+04:00) Tbilisi', + 'Asia/Yerevan' => '(UTC+04:00) Yerevan', + 'Asia/Kabul' => '(UTC+04:30) Kabul', + 'Asia/Islamabad' => '(UTC+05:00) Islamabad', + 'Asia/Karachi' => '(UTC+05:00) Karachi', + 'Asia/Tashkent' => '(UTC+05:00) Tashkent', + 'Asia/Calcutta' => '(UTC+05:30) Chennai', + 'Asia/Kolkata' => '(UTC+05:30) Kolkata', + 'Asia/Mumbai' => '(UTC+05:30) Mumbai', + 'Asia/New_Delhi' => '(UTC+05:30) New Delhi', + 'Asia/Sri_Jayawardenepura' => '(UTC+05:30) Sri Jayawardenepura', + 'Asia/Katmandu' => '(UTC+05:45) Kathmandu', + 'Asia/Almaty' => '(UTC+06:00) Almaty', + 'Asia/Astana' => '(UTC+06:00) Astana', + 'Asia/Dhaka' => '(UTC+06:00) Dhaka', + 'Asia/Yekaterinburg' => '(UTC+06:00) Ekaterinburg', + 'Asia/Rangoon' => '(UTC+06:30) Rangoon', + 'Asia/Bangkok' => '(UTC+07:00) Bangkok', + 'Asia/Hanoi' => '(UTC+07:00) Hanoi', + 'Asia/Jakarta' => '(UTC+07:00) Jakarta', + 'Asia/Novosibirsk' => '(UTC+07:00) Novosibirsk', + 'Asia/Beijing' => '(UTC+08:00) Beijing', + 'Asia/Chongqing' => '(UTC+08:00) Chongqing', + 'Asia/Hong_Kong' => '(UTC+08:00) Hong Kong', + 'Asia/Krasnoyarsk' => '(UTC+08:00) Krasnoyarsk', + 'Asia/Kuala_Lumpur' => '(UTC+08:00) Kuala Lumpur', + 'Australia/Perth' => '(UTC+08:00) Perth', + 'Asia/Singapore' => '(UTC+08:00) Singapore', + 'Asia/Taipei' => '(UTC+08:00) Taipei', + 'Asia/Ulan_Bator' => '(UTC+08:00) Ulaan Bataar', + 'Asia/Urumqi' => '(UTC+08:00) Urumqi', + 'Asia/Irkutsk' => '(UTC+09:00) Irkutsk', + 'Asia/Osaka' => '(UTC+09:00) Osaka', + 'Asia/Sapporo' => '(UTC+09:00) Sapporo', + 'Asia/Seoul' => '(UTC+09:00) Seoul', + 'Asia/Tokyo' => '(UTC+09:00) Tokyo', + 'Australia/Adelaide' => '(UTC+09:30) Adelaide', + 'Australia/Darwin' => '(UTC+09:30) Darwin', + 'Australia/Brisbane' => '(UTC+10:00) Brisbane', + 'Australia/Canberra' => '(UTC+10:00) Canberra', + 'Pacific/Guam' => '(UTC+10:00) Guam', + 'Australia/Hobart' => '(UTC+10:00) Hobart', + 'Australia/Melbourne' => '(UTC+10:00) Melbourne', + 'Pacific/Port_Moresby' => '(UTC+10:00) Port Moresby', + 'Australia/Sydney' => '(UTC+10:00) Sydney', + 'Asia/Yakutsk' => '(UTC+10:00) Yakutsk', + 'Asia/Vladivostok' => '(UTC+11:00) Vladivostok', + 'Pacific/Auckland' => '(UTC+12:00) Auckland', + 'Pacific/Fiji' => '(UTC+12:00) Fiji', + 'Pacific/Kwajalein' => '(UTC+12:00) International Date Line West', + 'Asia/Kamchatka' => '(UTC+12:00) Kamchatka', + 'Asia/Magadan' => '(UTC+12:00) Magadan', + 'Pacific/Marshall_Is' => '(UTC+12:00) Marshall Is.', + 'Asia/New_Caledonia' => '(UTC+12:00) New Caledonia', + 'Asia/Solomon_Is' => '(UTC+12:00) Solomon Is.', + 'Pacific/Wellington' => '(UTC+12:00) Wellington', + 'Pacific/Tongatapu' => '(UTC+13:00) Nuku\'alofa', + ]; + } +} + +if (!function_exists('get_country_list')) { + /** + * @return array + */ + function get_country_list() + { + return [ + 'AF' => 'Afghanistan', + 'AL' => 'Albania', + 'DZ' => 'Algeria', + 'AS' => 'American Samoa', + 'AD' => 'Andorra', + 'AO' => 'Angola', + 'AI' => 'Anguilla', + 'AQ' => 'Antarctica', + 'AG' => 'Antigua and Barbuda', + 'AR' => 'Argentina', + 'AM' => 'Armenia', + 'AW' => 'Aruba', + 'AU' => 'Australia', + 'AT' => 'Austria', + 'AZ' => 'Azerbaijan', + 'BS' => 'Bahamas', + 'BH' => 'Bahrain', + 'BD' => 'Bangladesh', + 'BB' => 'Barbados', + 'BY' => 'Belarus', + 'BE' => 'Belgium', + 'BZ' => 'Belize', + 'BJ' => 'Benin', + 'BM' => 'Bermuda', + 'BT' => 'Bhutan', + 'BO' => 'Bolivia', + 'BA' => 'Bosnia and Herzegovina', + 'BW' => 'Botswana', + 'BV' => 'Bouvet Island', + 'BR' => 'Brazil', + 'BQ' => 'British Antarctic Territory', + 'IO' => 'British Indian Ocean Territory', + 'VG' => 'British Virgin Islands', + 'BN' => 'Brunei', + 'BG' => 'Bulgaria', + 'BF' => 'Burkina Faso', + 'BI' => 'Burundi', + 'KH' => 'Cambodia', + 'CM' => 'Cameroon', + 'CA' => 'Canada', + 'CT' => 'Canton and Enderbury Islands', + 'CV' => 'Cape Verde', + 'KY' => 'Cayman Islands', + 'CF' => 'Central African Republic', + 'TD' => 'Chad', + 'CL' => 'Chile', + 'CN' => 'China', + 'CX' => 'Christmas Island', + 'CC' => 'Cocos [Keeling] Islands', + 'CO' => 'Colombia', + 'KM' => 'Comoros', + 'CG' => 'Congo - Brazzaville', + 'CD' => 'Congo - Kinshasa', + 'CK' => 'Cook Islands', + 'CR' => 'Costa Rica', + 'HR' => 'Croatia', + 'CU' => 'Cuba', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'CI' => 'Côte d’Ivoire', + 'DK' => 'Denmark', + 'DJ' => 'Djibouti', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'NQ' => 'Dronning Maud Land', + 'EC' => 'Ecuador', + 'EG' => 'Egypt', + 'SV' => 'El Salvador', + 'GQ' => 'Equatorial Guinea', + 'ER' => 'Eritrea', + 'EE' => 'Estonia', + 'ET' => 'Ethiopia', + 'FK' => 'Falkland Islands', + 'FO' => 'Faroe Islands', + 'FJ' => 'Fiji', + 'FI' => 'Finland', + 'FR' => 'France', + 'GF' => 'French Guiana', + 'PF' => 'French Polynesia', + 'TF' => 'French Southern Territories', + 'FQ' => 'French Southern and Antarctic Territories', + 'GA' => 'Gabon', + 'GM' => 'Gambia', + 'GE' => 'Georgia', + 'DE' => 'Germany', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GR' => 'Greece', + 'GL' => 'Greenland', + 'GD' => 'Grenada', + 'GP' => 'Guadeloupe', + 'GU' => 'Guam', + 'GT' => 'Guatemala', + 'GG' => 'Guernsey', + 'GN' => 'Guinea', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HT' => 'Haiti', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HK' => 'Hong Kong SAR China', + 'HU' => 'Hungary', + 'IS' => 'Iceland', + 'IN' => 'India', + 'ID' => 'Indonesia', + 'IR' => 'Iran', + 'IQ' => 'Iraq', + 'IE' => 'Ireland', + 'IM' => 'Isle of Man', + 'IL' => 'Israel', + 'IT' => 'Italy', + 'JM' => 'Jamaica', + 'JP' => 'Japan', + 'JE' => 'Jersey', + 'JT' => 'Johnston Island', + 'JO' => 'Jordan', + 'KZ' => 'Kazakhstan', + 'KE' => 'Kenya', + 'KI' => 'Kiribati', + 'KW' => 'Kuwait', + 'KG' => 'Kyrgyzstan', + 'LA' => 'Laos', + 'LV' => 'Latvia', + 'LB' => 'Lebanon', + 'LS' => 'Lesotho', + 'LR' => 'Liberia', + 'LY' => 'Libya', + 'LI' => 'Liechtenstein', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'MO' => 'Macau SAR China', + 'MK' => 'Macedonia', + 'MG' => 'Madagascar', + 'MW' => 'Malawi', + 'MY' => 'Malaysia', + 'MV' => 'Maldives', + 'ML' => 'Mali', + 'MT' => 'Malta', + 'MH' => 'Marshall Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MU' => 'Mauritius', + 'YT' => 'Mayotte', + 'FX' => 'Metropolitan France', + 'MX' => 'Mexico', + 'FM' => 'Micronesia', + 'MI' => 'Midway Islands', + 'MD' => 'Moldova', + 'MC' => 'Monaco', + 'MN' => 'Mongolia', + 'ME' => 'Montenegro', + 'MS' => 'Montserrat', + 'MA' => 'Morocco', + 'MZ' => 'Mozambique', + 'MM' => 'Myanmar [Burma]', + 'NA' => 'Namibia', + 'NR' => 'Nauru', + 'NP' => 'Nepal', + 'NL' => 'Netherlands', + 'AN' => 'Netherlands Antilles', + 'NT' => 'Neutral Zone', + 'NC' => 'New Caledonia', + 'NZ' => 'New Zealand', + 'NI' => 'Nicaragua', + 'NE' => 'Niger', + 'NG' => 'Nigeria', + 'NU' => 'Niue', + 'NF' => 'Norfolk Island', + 'KP' => 'North Korea', + 'VD' => 'North Vietnam', + 'MP' => 'Northern Mariana Islands', + 'NO' => 'Norway', + 'OM' => 'Oman', + 'PC' => 'Pacific Islands Trust Territory', + 'PK' => 'Pakistan', + 'PW' => 'Palau', + 'PS' => 'Palestinian Territories', + 'PA' => 'Panama', + 'PZ' => 'Panama Canal Zone', + 'PG' => 'Papua New Guinea', + 'PY' => 'Paraguay', + 'YD' => 'People\'s Democratic Republic of Yemen', + 'PE' => 'Peru', + 'PH' => 'Philippines', + 'PN' => 'Pitcairn Islands', + 'PL' => 'Poland', + 'PT' => 'Portugal', + 'PR' => 'Puerto Rico', + 'QA' => 'Qatar', + 'RO' => 'Romania', + 'RU' => 'Russia', + 'RW' => 'Rwanda', + 'RE' => 'Réunion', + 'BL' => 'Saint Barthélemy', + 'SH' => 'Saint Helena', + 'KN' => 'Saint Kitts and Nevis', + 'LC' => 'Saint Lucia', + 'MF' => 'Saint Martin', + 'PM' => 'Saint Pierre and Miquelon', + 'VC' => 'Saint Vincent and the Grenadines', + 'WS' => 'Samoa', + 'SM' => 'San Marino', + 'SA' => 'Saudi Arabia', + 'SN' => 'Senegal', + 'RS' => 'Serbia', + 'CS' => 'Serbia and Montenegro', + 'SC' => 'Seychelles', + 'SL' => 'Sierra Leone', + 'SG' => 'Singapore', + 'SK' => 'Slovakia', + 'SI' => 'Slovenia', + 'SB' => 'Solomon Islands', + 'SO' => 'Somalia', + 'ZA' => 'South Africa', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'KR' => 'South Korea', + 'ES' => 'Spain', + 'LK' => 'Sri Lanka', + 'SD' => 'Sudan', + 'SR' => 'Suriname', + 'SJ' => 'Svalbard and Jan Mayen', + 'SZ' => 'Swaziland', + 'SE' => 'Sweden', + 'CH' => 'Switzerland', + 'SY' => 'Syria', + 'ST' => 'São Tomé and Príncipe', + 'TW' => 'Taiwan', + 'TJ' => 'Tajikistan', + 'TZ' => 'Tanzania', + 'TH' => 'Thailand', + 'TL' => 'Timor-Leste', + 'TG' => 'Togo', + 'TK' => 'Tokelau', + 'TO' => 'Tonga', + 'TT' => 'Trinidad and Tobago', + 'TN' => 'Tunisia', + 'TR' => 'Turkey', + 'TM' => 'Turkmenistan', + 'TC' => 'Turks and Caicos Islands', + 'TV' => 'Tuvalu', + 'UM' => 'U.S. Minor Outlying Islands', + 'PU' => 'U.S. Miscellaneous Pacific Islands', + 'VI' => 'U.S. Virgin Islands', + 'UG' => 'Uganda', + 'UA' => 'Ukraine', + 'SU' => 'Union of Soviet Socialist Republics', + 'AE' => 'United Arab Emirates', + 'GB' => 'United Kingdom', + 'US' => 'United States', + 'ZZ' => 'Unknown or Invalid Region', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VU' => 'Vanuatu', + 'VA' => 'Vatican City', + 'VE' => 'Venezuela', + 'VN' => 'Vietnam', + 'WK' => 'Wake Island', + 'WF' => 'Wallis and Futuna', + 'EH' => 'Western Sahara', + 'YE' => 'Yemen', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', + 'AX' => 'Åland Islands', + ]; + } +} + +if (!function_exists('find_directories')) { + /** + * Gets directories inside the given path + * + * @param $path + * + * @return array + */ + function find_directories($path) + { + return array_filter(glob(rtrim($path, '/') . '/*', GLOB_ONLYDIR), function ($path) { + $name = basename($path); + + return $name[0] !== '_'; + }); + } +} + +if (!function_exists('find_files')) { + /** + * Find files inside $paths, directories and file name starting with "_" will be ignored. + * + * + * @param string $searchPaths + * @param int $flags + * @param string $pattern + * @param bool|int $includeSubDirectories + * @param callable - $ignore filter + * + * @return array + */ + function find_files($searchPaths, $flags = 0, $pattern = '', $includeSubDirectories = false) + { + if (!is_array($searchPaths)) { + $searchPaths = [$searchPaths]; + } + + $validPath = function ($path) { + $filename = pathinfo($path, PATHINFO_FILENAME); + + return $filename[0] !== '_'; + }; + + $filesPath = []; + foreach ($searchPaths as $searchPath) { + $searchPath = rtrim($searchPath, '/'); + $result = array_filter(glob($searchPath . '/' . rtrim($pattern, '/'), $flags), $validPath); + $filesPath = array_merge($filesPath, $result); + + if ($includeSubDirectories === true || (int)$includeSubDirectories > 0) { + if (is_numeric($includeSubDirectories)) { + $includeSubDirectories--; + } + + foreach (glob($searchPath . '/*', GLOB_ONLYDIR) as $subDir) { + if ($validPath($subDir)) { + $result = find_files($subDir, $flags, $pattern, $includeSubDirectories); + $filesPath = array_merge($filesPath, $result); + } + } + } + } + + return $filesPath; + } +} + +if (!function_exists('find_js_files')) { + /** + * Find JS files in the given path + * + * @param string $paths + * @param bool|int $includeSubDirectories + * + * @return array + */ + function find_js_files($paths, $includeSubDirectories = false) + { + return find_files($paths, 0, '*.js', $includeSubDirectories); + } +} + +if (!function_exists('find_json_files')) { + /** + * Find JSON files in the given path + * + * @param string $paths + * @param bool|int $includeSubDirectories + * + * @return array + */ + function find_json_files($paths, $includeSubDirectories = false) + { + return find_files($paths, 0, '*.json', $includeSubDirectories); + } +} + +if (!function_exists('find_php_files')) { + /** + * Find PHP files in the given path + * + * @param string $paths + * @param bool|int $includeSubDirectories + * + * @return array + */ + function find_php_files($paths, $includeSubDirectories = false) + { + return find_files($paths, 0, '*.php', $includeSubDirectories); + } +} + +if (!function_exists('find_html_files')) { + /** + * Find HTML files in the given path + * + * @param string $paths + * @param bool|int $includeSubDirectories + * + * @return array + */ + function find_html_files($paths, $includeSubDirectories = false) + { + return find_files($paths, 0, '*.html', $includeSubDirectories); + } +} + +if (!function_exists('find_twig_files')) { + /** + * Find Twig files in the given path + * + * @param string $paths + * @param bool|int $includeSubDirectories + * + * @return array + */ + function find_twig_files($paths, $includeSubDirectories = false) + { + return find_files($paths, 0, '*.twig', $includeSubDirectories); + } +} + +if (!function_exists('get_contents')) { + /** + * Get content from an URL + * + * @param $url + * @param $headers + * + * @return mixed + */ + function get_contents($url, $headers = []) + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_URL, $url); + + if ($headers) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + curl_close($ch); + + return $result; + } +} + +if (!function_exists('get_json')) { + /** + * Get json from an url + * + * @param $url + * @param array $headers + * + * @return mixed + */ + function get_json($url, $headers = []) + { + $content = get_contents($url, array_merge(['Content-Type: application/json'], $headers)); + + return json_decode($content, true); + } +} + +if (!function_exists('check_version')) { + /** + * Check Directus latest version + * + * @param bool $firstCheck + * + * @return array + */ + function check_version($firstCheck = false) + { + $data = [ + 'outdated' => false, + ]; + + $version = \Directus\Application\Application::DIRECTUS_VERSION; + + // ============================================================================= + // Getting the latest version, silently skip it if the server is no responsive. + // ============================================================================= + try { + $responseData = get_json('https://directus.io/check-version' . ($firstCheck ? '?first_check=1' : '')); + + if ($responseData && isset($responseData['success']) && $responseData['success'] == true) { + $versionData = $responseData['data']; + $data = array_merge($data, $versionData); + $data['outdated'] = version_compare($version, $versionData['current_version'], '<'); + } + } catch (\Exception $e) { + // Do nothing + } + + return $data; + } +} + +if (!function_exists('feedback_login_ping')) { + /** + * Sends a unique random token to help us understand approximately how many instances of Directus exist. + * This can be disabled in your config file. + * + * @param string $key + */ + function feedback_login_ping($key) + { + try { + get_json('https://directus.io/feedback/ping/' . (string) $key); + } catch (\Exception $e) { + // Do nothing + } + } +} + +if (!function_exists('get_request_ip')) { + function get_request_ip() + { + if (isset($_SERVER['X_FORWARDED_FOR'])) { + return $_SERVER['X_FORWARDED_FOR']; + } elseif (isset($_SERVER['CLIENT_IP'])) { + return $_SERVER['CLIENT_IP']; + } + + return $_SERVER['REMOTE_ADDR']; + } +} + +if (!function_exists('get_project_info')) { + function get_project_info() + { + /** @var \Directus\Database\TableGateway\DirectusSettingsTableGateway $settingsTable */ + $settingsTable = \Directus\Database\TableGatewayFactory::create('directus_settings', [ + 'acl' => null + ]); + $settings = $settingsTable->fetchCollection('global'); + + $projectName = isset($settings['project_name']) ? $settings['project_name'] : 'Directus'; + $defaultProjectLogo = get_directus_path('/assets/imgs/directus-logo-flat.svg'); + if (isset($settings['cms_thumbnail_url']) && $settings['cms_thumbnail_url']) { + $projectLogoURL = $settings['cms_thumbnail_url']; + $filesTable = \Directus\Database\TableGatewayFactory::create('directus_files', [ + 'acl' => null + ]); + $data = $filesTable->fetchItems([ + 'filter' => ['id' => $projectLogoURL] + ]); + + $projectLogoURL = \Directus\Util\ArrayUtils::get($data, 'url', $defaultProjectLogo); + } else { + $projectLogoURL = $defaultProjectLogo; + } + + return [ + 'project_name' => $projectName, + 'project_logo_url' => $projectLogoURL + ]; + } +} + +if (!function_exists('get_missing_requirements')) { + /** + * Gets an array of errors message when there's a missing requirements + * + * @return array + */ + function get_missing_requirements() + { + $errors = []; + + if (version_compare(PHP_VERSION, '5.6.0', '<')) { + $errors[] = 'Your host needs to use PHP 5.6.0 or higher to run this version of Directus!'; + } + + if (!defined('PDO::ATTR_DRIVER_NAME')) { + $errors[] = 'Your host needs to have PDO enabled to run this version of Directus!'; + } + + if (defined('PDO::ATTR_DRIVER_NAME') && !in_array('mysql', PDO::getAvailableDrivers())) { + $errors[] = 'Your host needs to have PDO MySQL Driver enabled to run this version of Directus!'; + } + + if (!extension_loaded('gd') || !function_exists('gd_info')) { + $errors[] = 'Your host needs to have GD Library enabled to run this version of Directus!'; + } + + if (!extension_loaded('fileinfo') || !class_exists('finfo')) { + $errors[] = 'Your host needs to have File Information extension enabled to run this version of Directus!'; + } + + if (!extension_loaded('curl') || !function_exists('curl_init')) { + $errors[] = 'Your host needs to have cURL extension enabled to run this version of Directus!'; + } + + if (!file_exists(base_path() . '/vendor/autoload.php')) { + $errors[] = 'Composer dependencies must be installed first.'; + } + + return $errors; + } +} + +if (!function_exists('display_missing_requirements_html')) { + /** + * Display an html error page + * + * @param array $errors + * @param \Directus\Application\Application $app + */ + function display_missing_requirements_html($errors, $app) + { + $projectInfo = get_project_info(); + + $data = array_merge($projectInfo, [ + 'errors' => $errors + ]); + + $app->response()->header('Content-Type', 'text/html; charset=utf-8'); + $app->render('errors/requirements.twig', $data); + } +} + +if (!function_exists('define_constant')) { + /** + * Define a constant if it does not exists + * + * @param string $name + * @param mixed $value + * + * @return bool + */ + function define_constant($name, $value) + { + $defined = true; + + if (!defined($name)) { + define($name, $value); + $defined = false; + } + + return $defined; + } +} + +if (!function_exists('get_columns_flat_at')) { + /** + * Get all the columns name in the given level + * + * @param array $columns + * @param int $level + * + * @return array + */ + function get_columns_flat_at(array $columns, $level = 0) + { + $names = []; + + foreach ($columns as $column) { + $parts = explode('.', $column); + + if (isset($parts[$level])) { + $names[] = $parts[$level]; + } + } + + return $names; + } +} + +if (!function_exists('get_csv_flat_columns')) { + /** + * Gets a CSV flat columns list from the given array + * + * @param array $columns + * @param null $prefix + * + * @return string + */ + function get_csv_flat_columns(array $columns, $prefix = null) + { + $flatColumns = []; + $prefix = $prefix === null ? '' : $prefix . '.'; + + foreach ($columns as $key => $value) { + if (is_array($value)) { + $value = get_csv_flat_columns($value, $prefix . $key); + } else { + $value = $prefix . $key; + } + + $flatColumns[] = $value; + } + + return implode(',', $flatColumns); + } +} + +if (!function_exists('get_array_flat_columns')) { + /** + * Gets an array flat columns list from the given array + * + * @param $columns + * + * @return array + */ + function get_array_flat_columns($columns) + { + // TODO: make sure array is passed??? + if (empty($columns)) { + return []; + } + + return explode(',', get_csv_flat_columns($columns ?: [])); + } +} +if (!function_exists('get_unflat_columns')) { + /** + * Gets the unflat version of flat (dot-notated) column list + * + * @param string|array $columns + * + * @return array + */ + function get_unflat_columns($columns) + { + $names = []; + + if (!is_array($columns)) { + $columns = explode(',', $columns); + } + + foreach ($columns as $column) { + $parts = explode('.', $column, 2); + + if (isset($parts[0])) { + if (!isset($names[$parts[0]])) { + $names[$parts[0]] = null; + } + + if (isset($parts[1])) { + if ($names[$parts[0]] === null) { + $names[$parts[0]] = []; + } + + $child = get_unflat_columns($parts[1]); + $names[$parts[0]][key($child)] = current($child); + }; + } + } + + return $names; + } +} + +if (!function_exists('column_identifier_reverse')) { + /** + * Reverse a dot notation column identifier + * + * Ex: posts.comments.author.email => email.author.comments.posts + * + * @param string $identifier + * + * @return string + */ + function column_identifier_reverse($identifier) + { + if (strpos($identifier, '.') === false) { + return $identifier; + } + + $parts = array_reverse(explode('.', $identifier)); + + return implode('.', $parts); + } +} + +if (!function_exists('compact_sort_to_array')) { + /** + * Converts compact sorting column to array + * + * Example: - to [field => 'DESC'] + * + * @param $field + * + * @return array + * + * @throws \Directus\Exception\Exception + */ + function compact_sort_to_array($field) + { + if (!is_string($field)) { + throw new \Directus\Exception\Exception(sprintf('field is expected to be string, %s given.', gettype($field))); + } + + $order = 'ASC'; + if (substr($field, 0, 1) === '-') { + $order = 'DESC'; + $field = substr($field, 1); + } + + return [ + $field => $order + ]; + } +} + +if (!function_exists('convert_param_columns')) { + function convert_param_columns($columns) + { + if (is_array($columns)) { + return $columns; + } + + if (is_string($columns)) { + // remove all 'falsy' columns name + $columns = array_filter(\Directus\Util\StringUtils::csv($columns, true)); + } else { + $columns = []; + } + + return $columns; + } +} + +if (!function_exists('is_valid_regex_pattern')) { + /** + * Checks whether the given pattern is a valid regex + * + * @param string $pattern + * + * @return bool + */ + function is_valid_regex_pattern($pattern) + { + $valid = false; + + if (is_string($pattern) && @preg_match($pattern, null) !== false) { + $valid = true; + } + + return $valid; + } +} diff --git a/src/helpers/app.php b/src/helpers/app.php new file mode 100644 index 0000000000..f7a87ff25a --- /dev/null +++ b/src/helpers/app.php @@ -0,0 +1,133 @@ +has('settings') ? $container->get('settings') : new \Directus\Collection\Collection(); + + if ($settings->get('env', 'development') === 'production') { + $response = $response->withStatus(404); + } else { + $body = new \Slim\Http\Body(fopen('php://temp', 'r+')); + $body->write('pong'); + $response = $response->withBody($body); + } + + return $response; + }; + } +} + +if (!function_exists('create_ping_route')) { + /** + * Create a new ping the server route + * + * @param $app + * + * @return \Directus\Application\Application + */ + function create_ping_route(\Directus\Application\Application $app) + { + /** + * Ping the server + */ + $app->get('/ping', ping_route($app))->setName('server_ping'); + + return $app; + } +} + +if (!function_exists('create_ping_server')) { + /** + * Creates a simple app + * + * @param string $basePath + * @param array $config + * + * @return \Directus\Application\Application + */ + function create_ping_server($basePath, array $config = []) + { + $app = create_app($basePath, array_merge([ + 'app' => [ + 'env' => 'production' + ] + ], $config)); + + create_ping_route($app); + + return $app; + } +} + +if (!function_exists('ping_server')) { + /** + * Ping the API Server + * + * @return bool + */ + function ping_server() + { + // @TODO: Fix error when the route exists but there's an error + // It will not return "pong" back + $response = @file_get_contents(get_url('/api/ping')); + + return $response === 'pong'; + } +} diff --git a/src/helpers/arrays.php b/src/helpers/arrays.php new file mode 100644 index 0000000000..3f2b6c2bb5 --- /dev/null +++ b/src/helpers/arrays.php @@ -0,0 +1,8 @@ +group('/' . trim($groupName, '/'), function () use ($endpoints, $app) { + foreach ($endpoints as $routePath => $endpoint) { + $isGroup = \Directus\Util\ArrayUtils::get($endpoint, 'group', false) === true; + + if ($isGroup) { + create_group_route_from_array( + $app, + $routePath, + (array) \Directus\Util\ArrayUtils::get($endpoint, 'endpoints', []) + ); + } else { + create_route_from_array($app, $routePath, $endpoint); + } + } + }); + } +} + +if (!function_exists('create_route_from_array')) { + /** + * Add a route to the given application + * + * @param \Directus\Application\Application $app + * @param string $routePath + * @param array $options + * + * @throws \Directus\Exception\Exception + */ + function create_route_from_array(\Directus\Application\Application $app, $routePath, array $options) + { + $methods = \Directus\Util\ArrayUtils::get($options, 'method', ['GET']); + if (!is_array($methods)) { + $methods = [$methods]; + } + + $handler = \Directus\Util\ArrayUtils::get($options, 'handler'); + if (!is_callable($handler) && !class_exists($handler)) { + throw new \Directus\Exception\Exception( + sprintf('Endpoints handler must be a callable, but %s was given', gettype($handler)) + ); + } + + $app->map($methods, $routePath, $handler); + } +} + +if (!function_exists('get_custom_hooks')) { + /** + * Get a list of hooks in the given path + * + * @param string $path + * @param bool $onlyDirectories + * + * @return array + */ + function get_custom_hooks($path, $onlyDirectories = false) + { + return get_custom_x('hooks', $path, $onlyDirectories); + } +} diff --git a/src/helpers/file.php b/src/helpers/file.php new file mode 100644 index 0000000000..55f3d83c6a --- /dev/null +++ b/src/helpers/file.php @@ -0,0 +1,170 @@ +getContainer(); + $thumbnailDimensions = array_filter( + explode(',', get_directus_setting('thumbnailer', 'dimensions')) + ); + + // Add default size + array_unshift($thumbnailDimensions, '200x200'); + + $config = $container->get('config'); + $fileRootUrl = $config->get('filesystem.root_url'); + $hasFileRootUrlHost = parse_url($fileRootUrl, PHP_URL_HOST); + $isLocalStorageAdapter = $config->get('filesystem.adapter') == 'local'; + $list = isset($rows[0]); + + if (!$list) { + $rows = [$rows]; + } + + foreach ($rows as &$row) { + $storage = []; + $thumbnailFilenameParts = explode('.', $row['filename']); + $thumbnailExtension = array_pop($thumbnailFilenameParts); + $storage['url'] = $storage['full_url'] = $fileRootUrl . '/' . $row['filename']; + + // Add Full url + if ($isLocalStorageAdapter && !$hasFileRootUrlHost) { + $storage['full_url'] = get_url($storage['url']); + } + + // Add Thumbnails + foreach (array_unique($thumbnailDimensions) as $dimension) { + if (\Directus\Filesystem\Thumbnail::isNonImageFormatSupported($thumbnailExtension)) { + $thumbnailExtension = \Directus\Filesystem\Thumbnail::defaultFormat(); + } + + if (!is_string($dimension)) { + continue; + } + + $size = explode('x', $dimension); + if (count($size) == 2) { + $thumbnailUrl = get_thumbnail_url($row['filename'], $size[0], $size[1]); + $storage['thumbnails'][] = [ + 'full_url' => $thumbnailUrl, + 'url' => $thumbnailUrl, + 'dimension' => $dimension, + 'width' => $size[0], + 'height' => $size[1] + ]; + } + } + + // Add embed content + /** @var \Directus\Embed\EmbedManager $embedManager */ + $embedManager = $container->get('embed_manager'); + $provider = isset($row['type']) ? $embedManager->getByType($row['type']) : null; + $embed = null; + if ($provider) { + $embed = [ + 'html' => $provider->getCode($row), + 'url' => $provider->getUrl($row) + ]; + } + $storage['embed'] = $embed; + + $row['storage'] = $storage; + } + + return $list ? $rows : reset($rows); + } +} + +if (!function_exists('get_thumbnail_url')) +{ + /** + * Returns a url to the thumbnailer + * + * @param string $name + * @param int $width + * @param int $height + * @param string $mode + * @param string $quality + * + * @return string + */ + function get_thumbnail_url($name, $width, $height, $mode = 'crop', $quality = 'good') + { + // width/height/mode/quality/name + return get_url(sprintf( + 'thumbnail/%d/%d/%s/%s/%s', + $width, $height, $mode, $quality, $name + )); + } +} diff --git a/src/helpers/items.php b/src/helpers/items.php new file mode 100644 index 0000000000..f5ceba88c9 --- /dev/null +++ b/src/helpers/items.php @@ -0,0 +1,82 @@ +getContainer()->get('database'); + $tableGateway = new \Zend\Db\TableGateway\TableGateway($collection, $dbConnection); + /** @var \Directus\Database\TableGateway\RelationalTableGateway $tableGateway */ + $usersTableGateway = \Directus\Database\TableGatewayFactory::create($collection, [ + 'connection' => $dbConnection, + 'acl' => false + ]); + + /** @var \Directus\Database\Schema\SchemaManager $schemaManager */ + $schemaManager = $app->getContainer()->get('schema_manager'); + + $collectionObject = $schemaManager->getCollection($collection); + $userCreatedField = $collectionObject->getUserCreatedField(); + + $owner = null; + if ($userCreatedField) { + $fieldName = $userCreatedField->getName(); + $select = new \Zend\Db\Sql\Select( + ['c' => $tableGateway->table] + ); + $select->limit(1); + $select->columns([]); + $select->where([ + 'c.' . $collectionObject->getPrimaryKeyName() => $id + ]); + + $subSelect = new \Zend\Db\Sql\Select('directus_user_roles'); + + $select->join( + ['ur' => $subSelect], + sprintf('c.%s = ur.user', $fieldName), + [ + 'id' => 'user', + 'role' + ], + $select::JOIN_LEFT + ); + + $owner = $tableGateway->selectWith($select)->toArray(); + $owner = $usersTableGateway->parseRecord(reset($owner), 'directus_users'); + } + + return $owner; + } +} + +if (!function_exists('get_user_ids_in_group')) { + function get_user_ids_in_group(array $roleIds) + { + $id = array_shift($roleIds); + $app = \Directus\Application\Application::getInstance(); + $dbConnection = $app->getContainer()->get('database'); + $tableGateway = new \Zend\Db\TableGateway\TableGateway('directus_user_roles', $dbConnection); + + $select = new \Zend\Db\Sql\Select($tableGateway->table); + $select->columns(['id' => 'user']); + $select->where(['role' => $id]); + + $result = $tableGateway->selectWith($select); + + $ids = []; + foreach ($result as $row) { + $ids[] = $row->id; + } + + return $ids; + } +} diff --git a/src/helpers/mail.php b/src/helpers/mail.php new file mode 100644 index 0000000000..ff93c15d86 --- /dev/null +++ b/src/helpers/mail.php @@ -0,0 +1,118 @@ +getContainer()->get('mailer'); + + $mailer->send($viewPath, $data, $callback); + } +} + +if (!function_exists('parse_twig')) { + /** + * Parse twig view + * + * @param string $viewPath + * @param array $data + * + * @return string + */ + function parse_twig($viewPath, array $data) + { + $app = \Directus\Application\Application::getInstance(); + + $mailSettings = []; + $settings = $app->getContainer()->get('app_settings'); + foreach ($settings as $setting) { + $mailSettings[$setting['scope']][$setting['key']] = $setting['value']; + } + + $data = array_merge(['settings' => $mailSettings], $data); + + return $app->getContainer()->get('mail_view')->fetch($viewPath, $data); + } +} + +if (!function_exists('send_reset_password_email')) { + /** + * Sends a new password email + * + * @param $user + * @param string $password + */ + function send_reset_password_email($user, $password) + { + $data = ['new_password' => $password]; + send_email('reset-password.twig', $data, function (\Directus\Mail\Message $message) use ($user) { + $message->setSubject( + sprintf('New Temporary Password: %s', get_directus_setting('global', 'project_name', '')) + ); + $message->setTo($user['email']); + }); + } +} + +if (!function_exists('send_forgot_password_email')) { + /** + * Sends a new reset password email + * + * @param $user + * @param string $token + */ + function send_forgot_password_email($user, $token) + { + $data = ['reset_token' => $token]; + send_email('forgot-password.twig', $data, function (\Directus\Mail\Message $message) use ($user) { + $message->setSubject( + sprintf('Password Reset Request: %s', get_directus_setting('global', 'project_name', '')) + ); + $message->setTo($user['email']); + }); + } +} + +if (!function_exists('send_new_install_email')) { + /** + * Sends a new installation email + * + * @param array $data + */ + function send_new_install_email(array $data) + { + send_email('new-install.twig', $data, function (\Directus\Mail\Message $message) use ($data) { + $message->setSubject( + sprintf('Your New Instance: %s', get_directus_setting('global', 'project_name', '')) + ); + $message->setTo($data['user']['email']); + }); + } +} + +if (!function_exists('send_user_invitation_email')) { + /** + * Sends a invitation email + * + * @param string $email + * @param string $token + */ + function send_user_invitation_email($email, $token) + { + $data = ['token' => $token]; + send_email('user-invitation.twig', $data, function (\Directus\Mail\Message $message) use ($email) { + $message->setSubject( + sprintf('Invitation to Instance: %s', get_directus_setting('global', 'project_name', '')) + ); + $message->setTo($email); + }); + } +} diff --git a/src/helpers/server.php b/src/helpers/server.php new file mode 100644 index 0000000000..0591c1e471 --- /dev/null +++ b/src/helpers/server.php @@ -0,0 +1,22 @@ +getContainer()->get('app_settings'); + + if ($scope !== null) { + foreach ($settings as $index => $setting) { + if ($setting['scope'] !== $scope) { + unset($settings[$index]); + } + } + } + + return $settings; + } +} + +if (!function_exists('get_directus_setting')) { + /** + * Returns a directus settings by key+scope combo + * + * @param string $scope + * @param string $key + * @param null $default + * + * @return mixed + */ + function get_directus_setting($scope, $key, $default = null) + { + $settings = get_directus_settings(); + $value = $default; + + foreach ($settings as $setting) { + if ($setting['scope'] == $scope && $setting['key'] == $key) { + $value = $setting['value']; + break; + } + } + + return $value; + } +} + +if (!function_exists('get_kv_directus_settings')) { + /** + * Returns the settings in a key-value format + * + * @param null|string $scope + * + * @return array + */ + function get_kv_directus_settings($scope = null) + { + $settings = get_directus_settings($scope); + $result = []; + + foreach ($settings as $setting) { + $result[$setting['key']] = $setting['value']; + } + + return $result; + } +} diff --git a/src/helpers/sorting.php b/src/helpers/sorting.php new file mode 100644 index 0000000000..8301f669f9 --- /dev/null +++ b/src/helpers/sorting.php @@ -0,0 +1,27 @@ +toString(); + } +} + +if (!function_exists('generate_uuid3')) { + /** + * Generates a UUID v3 string + * + * @param string $namespace + * @param string $name + * + * @return string + */ + function generate_uui3($namespace, $name) + { + return \Ramsey\Uuid\Uuid::uuid3( + $namespace, + $name + )->toString(); + } +} + +if (!function_exists('generate_uuid4')) { + /** + * Generates a UUID v4 string + * + * @return string + */ + function generate_uui4() + { + return \Ramsey\Uuid\Uuid::uuid4()->toString(); + } +} diff --git a/src/mail/base.twig b/src/mail/base.twig new file mode 100644 index 0000000000..d4ed13a352 --- /dev/null +++ b/src/mail/base.twig @@ -0,0 +1,63 @@ + + + + + Directus Email Service + + + + + + + + + +
    + + + + + + + + + + +
    + Directus +
    + + + + +
    + {% block content %}{% endblock %} +
    +
    + + + + +
    + {% block footer %} + {% include 'footer.twig' %} + {% endblock %} +
    +
    +
    + + diff --git a/src/mail/footer.twig b/src/mail/footer.twig new file mode 100644 index 0000000000..b01dfa6838 --- /dev/null +++ b/src/mail/footer.twig @@ -0,0 +1,7 @@ +

    + This email was sent by Directus – {{settings.global.project.name }} +

    +

    + Log in + to manage your email preferences +

    diff --git a/src/mail/forgot-password.twig b/src/mail/forgot-password.twig new file mode 100644 index 0000000000..310c972f5c --- /dev/null +++ b/src/mail/forgot-password.twig @@ -0,0 +1,13 @@ +{% extends "base.twig" %} +{% block content %} + +

    Hey there,

    + +

    You requested to reset your password, here is your reset password link:

    + +{% set reset_url = settings.global.project.url|trim('/') ~ '/' ~ api.env ~ '/auth/reset_password/' ~ reset_token %} +

    {{ reset_url }}

    + +

    Love,
    Directus

    + +{% endblock %} diff --git a/src/mail/new-install.twig b/src/mail/new-install.twig new file mode 100644 index 0000000000..e22aea5445 --- /dev/null +++ b/src/mail/new-install.twig @@ -0,0 +1,25 @@ +{% extends "base.twig" %} +{% block content %} +

    Your new Instance of Directus is ready to go! You can access it using the following credentials:

    + +

    {{ project.url }}

    + +

    Main Configuration

    +

    + Project Name: {{ project.name }}
    + Admin Email: {{ user.email }}
    + Admin Password: {{ user.password }}
    + Installed Version: {{ project.version }}
    + API Key: {{ user.token }} +

    + +

    Database Configuration

    +

    + Host Name: {{ database.host }}
    + Username: {{ database.user }}
    + Password: {{ database.password }}
    + Database Name: {{ database.name }} +

    + +

    Love,
    Directus

    +{% endblock %} diff --git a/src/mail/reset-password.twig b/src/mail/reset-password.twig new file mode 100644 index 0000000000..26a8e28bd2 --- /dev/null +++ b/src/mail/reset-password.twig @@ -0,0 +1,14 @@ +{% extends "base.twig" %} +{% block content %} + +

    Hey there,

    + +

    Here is a temporary password to access Directus:

    + +

    {{ new_password }}

    + +

    Once you log in, you can change your password via the User Settings menu.

    + +

    Love,
    Directus

    + +{% endblock %} diff --git a/src/mail/user-invitation.twig b/src/mail/user-invitation.twig new file mode 100644 index 0000000000..33c4558b31 --- /dev/null +++ b/src/mail/user-invitation.twig @@ -0,0 +1,10 @@ +{% extends "base.twig" %} +{% block content %} + +

    You have been invited to {{settings.global.project.name }}. Please click the link below to join:

    + +{% set invitation_url = settings.global.project.url|trim('/') ~ '/' ~ api.env ~ '/auth/invitation/' ~ token %} +

    {{ invitation_url }}

    + +

    Love,
    Directus

    +{% endblock %} diff --git a/src/schema.sql b/src/schema.sql new file mode 100644 index 0000000000..759c399273 --- /dev/null +++ b/src/schema.sql @@ -0,0 +1,581 @@ +# ************************************************************ +# Sequel Pro SQL dump +# Version 4541 +# +# http://www.sequelpro.com/ +# https://github.com/sequelpro/sequelpro +# +# Host: localhost (MySQL 5.6.38) +# Database: directus +# Generation Time: 2018-06-11 15:01:55 +0000 +# ************************************************************ + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# Dump of table directus_activity +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_activity`; + +CREATE TABLE `directus_activity` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `type` varchar(45) NOT NULL, + `action` varchar(45) NOT NULL, + `user` int(11) unsigned NOT NULL DEFAULT '0', + `datetime` datetime NOT NULL, + `ip` varchar(50) NOT NULL, + `user_agent` varchar(255) NOT NULL, + `collection` varchar(64) NOT NULL, + `item` varchar(255) NOT NULL, + `datetime_edited` datetime DEFAULT NULL, + `comment` text, + `deleted_comment` tinyint(1) unsigned DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_activity_read +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_activity_read`; + +CREATE TABLE `directus_activity_read` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `activity` int(11) unsigned NOT NULL, + `user` int(11) unsigned NOT NULL DEFAULT '0', + `read` tinyint(1) unsigned NOT NULL DEFAULT '0', + `archived` tinyint(1) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_collection_presets +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_collection_presets`; + +CREATE TABLE `directus_collection_presets` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(255) DEFAULT NULL, + `user` int(11) unsigned DEFAULT NULL, + `role` int(11) unsigned DEFAULT NULL, + `collection` varchar(64) NOT NULL, + `search_query` varchar(100) DEFAULT NULL, + `filters` text, + `view_type` varchar(100) NOT NULL, + `view_query` text, + `view_options` text, + `translation` text, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_user_collection_title` (`user`,`collection`,`title`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_collections +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_collections`; + +CREATE TABLE `directus_collections` ( + `collection` varchar(64) NOT NULL, + `item_name_template` varchar(255) DEFAULT NULL, + `preview_url` varchar(255) DEFAULT NULL, + `hidden` tinyint(1) unsigned NOT NULL DEFAULT '0', + `single` tinyint(1) unsigned NOT NULL DEFAULT '0', + `translation` text, + `note` varchar(255) DEFAULT NULL, + PRIMARY KEY (`collection`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_fields +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_fields`; + +CREATE TABLE `directus_fields` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `collection` varchar(64) NOT NULL, + `field` varchar(64) NOT NULL, + `type` varchar(64) NOT NULL, + `interface` varchar(64) NOT NULL, + `options` text, + `locked` tinyint(1) unsigned NOT NULL DEFAULT '0', + `translation` text, + `readonly` tinyint(1) unsigned NOT NULL DEFAULT '0', + `required` tinyint(1) unsigned NOT NULL DEFAULT '0', + `sort` int(11) unsigned DEFAULT NULL, + `view_width` int(11) unsigned NOT NULL DEFAULT '4', + `note` varchar(1024) DEFAULT NULL, + `hidden_input` tinyint(1) unsigned NOT NULL DEFAULT '0', + `validation` varchar(255) DEFAULT NULL, + `hidden_list` tinyint(1) unsigned NOT NULL DEFAULT '0', + `group` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_collection_field` (`collection`,`field`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_fields` WRITE; +/*!40000 ALTER TABLE `directus_fields` DISABLE KEYS */; + +INSERT INTO `directus_fields` (`id`, `collection`, `field`, `type`, `interface`, `options`, `locked`, `translation`, `readonly`, `required`, `sort`, `view_width`, `note`, `hidden_input`, `validation`, `hidden_list`, `group`) +VALUES + (1,'directus_activity','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (2,'directus_activity','type','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (3,'directus_activity','action','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (4,'directus_activity','user','int','user',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (5,'directus_activity','datetime','datetime','datetime',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (6,'directus_activity','ip','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (7,'directus_activity','user_agent','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (8,'directus_activity','collection','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (9,'directus_activity','item','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (10,'directus_activity','comment','text','markdown',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (11,'directus_activity_read','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (12,'directus_activity_read','activity','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (13,'directus_activity_read','user','int','user',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (14,'directus_activity_read','read','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (15,'directus_activity_read','archived','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (16,'directus_collections','collection','varchar','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (17,'directus_collections','item_name_template','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (18,'directus_collections','preview_url','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (19,'directus_collections','hidden','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (20,'directus_collections','single','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (21,'directus_collections','translation','json','JSON',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (22,'directus_collections','note','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (23,'directus_collection_presets','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (24,'directus_collection_presets','title','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (25,'directus_collection_presets','user','int','user',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (26,'directus_collection_presets','role','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (27,'directus_collection_presets','collection','varchar','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (28,'directus_collection_presets','search_query','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (29,'directus_collection_presets','filters','json','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (30,'directus_collection_presets','view_options','json','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (31,'directus_collection_presets','view_type','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (32,'directus_collection_presets','view_query','json','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (33,'directus_collection_presets','translation','json','JSON',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (34,'directus_fields','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (35,'directus_fields','collection','varchar','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (36,'directus_fields','field','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (37,'directus_fields','type','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (38,'directus_fields','interface','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (39,'directus_fields','options','json','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (40,'directus_fields','locked','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (41,'directus_fields','translation','json','JSON',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (42,'directus_fields','readonly','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (43,'directus_fields','required','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (44,'directus_fields','sort','int','sort',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (45,'directus_fields','note','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (46,'directus_fields','hidden_input','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (47,'directus_fields','hidden_list','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (48,'directus_fields','view_width','int','numeric',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (49,'directus_fields','group','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (50,'directus_files','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (51,'directus_files','filename','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (52,'directus_files','title','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (53,'directus_files','description','text','textarea',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (54,'directus_files','location','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (55,'directus_files','tags','csv','tags',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (56,'directus_files','width','int','numeric',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (57,'directus_files','height','int','numeric',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (58,'directus_files','filesize','int','filesize',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (59,'directus_files','duration','int','numeric',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (60,'directus_files','metadata','json','JSON',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (61,'directus_files','type','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (62,'directus_files','charset','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (63,'directus_files','embed','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (64,'directus_files','folder','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (65,'directus_files','upload_user','int','user',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (66,'directus_files','upload_date','datetime','datetime',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (67,'directus_files','storage_adapter','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (68,'directus_files','data','blob','blob','{ \"nameField\": \"filename\", \"sizeField\": \"filesize\", \"typeField\": \"type\" }',0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (69,'directus_files','url','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (70,'directus_files','storage','alias','file-upload',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (71,'directus_folders','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (72,'directus_folders','name','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (73,'directus_folders','parent_folder','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (74,'directus_roles','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (75,'directus_roles','name','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (76,'directus_roles','description','varchar','textarea',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (77,'directus_roles','ip_whitelist','text','textarea',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (78,'directus_roles','nav_blacklist','text','textarea',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (79,'directus_user_roles','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (80,'directus_user_roles','user','int','user',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (81,'directus_user_roles','role','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (82,'directus_users','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (83,'directus_users','status','int','status','{\"status_mapping\":[{\"name\": \"draft\"},{\"name\": \"active\"},{\"name\": \"delete\"}]}',0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (84,'directus_users','first_name','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (85,'directus_users','last_name','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (86,'directus_users','email','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (87,'directus_users','roles','m2m','m2m',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (88,'directus_users','email_notifications','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (89,'directus_users','password','varchar','password',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (90,'directus_users','avatar','file','single-file',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (91,'directus_users','company','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (92,'directus_users','title','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (93,'directus_users','locale','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (94,'directus_users','locale_options','json','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (95,'directus_users','timezone','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (96,'directus_users','last_ip','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (97,'directus_users','last_login','datetime','datetime',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (98,'directus_users','last_access','datetime','datetime',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (99,'directus_users','last_page','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (100,'directus_users','token','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (101,'directus_users','invite_token','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (102,'directus_users','invite_accepted','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (103,'directus_permissions','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (104,'directus_permissions','collection','varchar','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (105,'directus_permissions','role','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (106,'directus_permissions','status','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (107,'directus_permissions','create','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (108,'directus_permissions','read','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (109,'directus_permissions','update','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (110,'directus_permissions','delete','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (111,'directus_permissions','navigate','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (112,'directus_permissions','explain','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (113,'directus_permissions','allow_statuses','csv','tags',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (114,'directus_permissions','read_field_blacklist','varchar','textarea',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (115,'directus_relations','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (116,'directus_relations','collection_a','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (117,'directus_relations','field_a','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (118,'directus_relations','junction_key_a','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (119,'directus_relations','junction_mixed_collections','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (120,'directus_relations','junction_key_b','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (121,'directus_relations','collection_b','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (122,'directus_relations','field_b','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (123,'directus_revisions','id','int','primary-key',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (124,'directus_revisions','activity','int','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (125,'directus_revisions','collection','varchar','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (126,'directus_revisions','item','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (127,'directus_revisions','data','longjson','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (128,'directus_revisions','delta','longjson','json',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (129,'directus_revisions','parent_item','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (130,'directus_revisions','parent_collection','varchar','many-to-one',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (131,'directus_revisions','parent_changed','boolean','toggle',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (132,'directus_settings','auto_sign_out','int','numeric',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL), + (133,'directus_settings','youtube_api_key','varchar','text-input',NULL,0,NULL,0,0,NULL,4,NULL,0,NULL,0,NULL); + +/*!40000 ALTER TABLE `directus_fields` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_files +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_files`; + +CREATE TABLE `directus_files` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `filename` varchar(255) NOT NULL, + `title` varchar(255) DEFAULT NULL, + `description` text, + `location` varchar(200) DEFAULT NULL, + `tags` varchar(255) DEFAULT NULL, + `width` int(11) unsigned DEFAULT NULL, + `height` int(11) unsigned DEFAULT NULL, + `filesize` int(11) unsigned NOT NULL DEFAULT '0', + `duration` int(11) DEFAULT NULL, + `metadata` text, + `type` varchar(255) DEFAULT NULL, + `charset` varchar(50) DEFAULT NULL, + `embed` varchar(200) DEFAULT NULL, + `folder` int(11) unsigned DEFAULT NULL, + `upload_user` int(11) unsigned NOT NULL, + `upload_date` datetime NOT NULL, + `storage_adapter` varchar(50) NOT NULL DEFAULT 'local', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_files` WRITE; +/*!40000 ALTER TABLE `directus_files` DISABLE KEYS */; + +INSERT INTO `directus_files` (`id`, `filename`, `title`, `description`, `location`, `tags`, `width`, `height`, `filesize`, `duration`, `metadata`, `type`, `charset`, `embed`, `folder`, `upload_user`, `upload_date`, `storage_adapter`) +VALUES + (1,'00000000001.jpg','Mountain Range','A gorgeous view of this wooded mountain range','Earth','trees,rocks,nature,mountains,forest',1800,1200,602058,NULL,NULL,'image/jpeg','binary',NULL,NULL,1,'2018-06-11 13:04:38','local'); + +/*!40000 ALTER TABLE `directus_files` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_folders +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_folders`; + +CREATE TABLE `directus_folders` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) CHARACTER SET utf8mb4 NOT NULL, + `parent_folder` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_name_parent_folder` (`name`,`parent_folder`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_migrations +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_migrations`; + +CREATE TABLE `directus_migrations` ( + `version` bigint(20) NOT NULL, + `migration_name` varchar(100) DEFAULT NULL, + `start_time` timestamp NULL DEFAULT NULL, + `end_time` timestamp NULL DEFAULT NULL, + `breakpoint` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`version`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_migrations` WRITE; +/*!40000 ALTER TABLE `directus_migrations` DISABLE KEYS */; + +INSERT INTO `directus_migrations` (`version`, `migration_name`, `start_time`, `end_time`, `breakpoint`) +VALUES + (20180220023138,'CreateActivityTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023144,'CreateActivityReadTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023152,'CreateCollectionsPresetsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023157,'CreateCollectionsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023202,'CreateFieldsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023208,'CreateFilesTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023213,'CreateFoldersTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023217,'CreateRolesTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023226,'CreatePermissionsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023232,'CreateRelationsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023238,'CreateRevisionsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023243,'CreateSettingsTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180220023248,'CreateUsersTable','2018-06-11 15:04:38','2018-06-11 15:04:38',0), + (20180426173310,'CreateUserRoles','2018-06-11 15:04:38','2018-06-11 15:04:38',0); + +/*!40000 ALTER TABLE `directus_migrations` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_permissions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_permissions`; + +CREATE TABLE `directus_permissions` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `collection` varchar(64) NOT NULL, + `role` int(11) unsigned NOT NULL, + `status` varchar(64) DEFAULT NULL, + `create` varchar(16) DEFAULT NULL, + `read` varchar(16) DEFAULT NULL, + `update` varchar(16) DEFAULT NULL, + `delete` varchar(16) DEFAULT NULL, + `navigate` tinyint(1) unsigned NOT NULL DEFAULT '0', + `comment` varchar(8) DEFAULT NULL, + `read_field_blacklist` varchar(1000) DEFAULT NULL, + `write_field_blacklist` varchar(1000) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_relations +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_relations`; + +CREATE TABLE `directus_relations` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `collection_a` varchar(64) NOT NULL, + `field_a` varchar(45) NOT NULL, + `junction_key_a` varchar(64) DEFAULT NULL, + `junction_collection` varchar(64) DEFAULT NULL, + `junction_mixed_collections` varchar(64) DEFAULT NULL, + `junction_key_b` varchar(64) DEFAULT NULL, + `collection_b` varchar(64) DEFAULT NULL, + `field_b` varchar(64) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_relations` WRITE; +/*!40000 ALTER TABLE `directus_relations` DISABLE KEYS */; + +INSERT INTO `directus_relations` (`id`, `collection_a`, `field_a`, `junction_key_a`, `junction_collection`, `junction_mixed_collections`, `junction_key_b`, `collection_b`, `field_b`) +VALUES + (1,'directus_activity','user',NULL,NULL,NULL,NULL,'directus_users',NULL), + (2,'directus_activity_read','user',NULL,NULL,NULL,NULL,'directus_users',NULL), + (3,'directus_activity_read','activity',NULL,NULL,NULL,NULL,'directus_activity',NULL), + (4,'directus_collections_presets','user',NULL,NULL,NULL,NULL,'directus_users',NULL), + (5,'directus_collections_presets','group',NULL,NULL,NULL,NULL,'directus_groups',NULL), + (6,'directus_files','upload_user',NULL,NULL,NULL,NULL,'directus_users',NULL), + (7,'directus_files','folder',NULL,NULL,NULL,NULL,'directus_folders',NULL), + (8,'directus_folders','parent_folder',NULL,NULL,NULL,NULL,'directus_folders',NULL), + (9,'directus_permissions','group',NULL,NULL,NULL,NULL,'directus_groups',NULL), + (10,'directus_revisions','activity',NULL,NULL,NULL,NULL,'directus_activity',NULL), + (11,'directus_users','roles','user','directus_user_roles',NULL,'role','directus_roles','users'), + (12,'directus_users','avatar',NULL,NULL,NULL,NULL,'directus_files',NULL); + +/*!40000 ALTER TABLE `directus_relations` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_revisions +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_revisions`; + +CREATE TABLE `directus_revisions` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `activity` int(11) unsigned NOT NULL, + `collection` varchar(64) NOT NULL, + `item` varchar(255) NOT NULL, + `data` longtext NOT NULL, + `delta` longtext, + `parent_item` varchar(255) DEFAULT NULL, + `parent_collection` varchar(64) DEFAULT NULL, + `parent_changed` tinyint(1) unsigned DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table directus_roles +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_roles`; + +CREATE TABLE `directus_roles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `external_id` varchar(255) DEFAULT NULL, + `name` varchar(100) NOT NULL, + `description` varchar(500) DEFAULT NULL, + `ip_whitelist` text, + `nav_blacklist` text, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_group_name` (`name`), + UNIQUE KEY `idx_users_external_id` (`external_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_roles` WRITE; +/*!40000 ALTER TABLE `directus_roles` DISABLE KEYS */; + +INSERT INTO `directus_roles` (`id`, `external_id`, `name`, `description`, `ip_whitelist`, `nav_blacklist`) +VALUES + (1,NULL,'Administrator','Admins have access to all managed data within the system by default',NULL,NULL), + (2,NULL,'Public','This sets the data that is publicly available through the API without a token',NULL,NULL); + +/*!40000 ALTER TABLE `directus_roles` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_settings +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_settings`; + +CREATE TABLE `directus_settings` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `scope` varchar(64) NOT NULL, + `key` varchar(64) NOT NULL, + `value` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_scope_name` (`scope`,`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_settings` WRITE; +/*!40000 ALTER TABLE `directus_settings` DISABLE KEYS */; + +INSERT INTO `directus_settings` (`id`, `scope`, `key`, `value`) +VALUES + (1,'global','auto_sign_out','60'), + (2,'global','project_name','Directus'), + (3,'global','default_limit','200'), + (4,'global','logo',''), + (5,'files','file_naming','file_id'), + (6,'files','youtube_api_key',''); + +/*!40000 ALTER TABLE `directus_settings` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_user_roles +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_user_roles`; + +CREATE TABLE `directus_user_roles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `user` int(11) unsigned DEFAULT NULL, + `role` int(11) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_user_role` (`user`,`role`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_user_roles` WRITE; +/*!40000 ALTER TABLE `directus_user_roles` DISABLE KEYS */; + +INSERT INTO `directus_user_roles` (`id`, `user`, `role`) +VALUES + (1,1,1); + +/*!40000 ALTER TABLE `directus_user_roles` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table directus_users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `directus_users`; + +CREATE TABLE `directus_users` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `status` int(1) unsigned NOT NULL DEFAULT '2', + `first_name` varchar(50) DEFAULT NULL, + `last_name` varchar(50) DEFAULT NULL, + `email` varchar(128) NOT NULL, + `email_notifications` int(1) NOT NULL DEFAULT '1', + `password` varchar(255) DEFAULT NULL, + `avatar` int(11) unsigned DEFAULT NULL, + `company` varchar(191) DEFAULT NULL, + `title` varchar(191) DEFAULT NULL, + `locale` varchar(8) DEFAULT 'en-US', + `high_contrast_mode` tinyint(1) unsigned DEFAULT '0', + `locale_options` text, + `timezone` varchar(32) NOT NULL DEFAULT 'America/New_York', + `last_ip` varchar(50) DEFAULT NULL, + `last_login` datetime DEFAULT NULL, + `last_access` datetime DEFAULT NULL, + `last_page` varchar(45) DEFAULT NULL, + `token` varchar(255) DEFAULT NULL, + `invite_token` varchar(255) DEFAULT NULL, + `invite_accepted` tinyint(1) unsigned DEFAULT '0', + `external_id` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_users_email` (`email`), + UNIQUE KEY `idx_users_token` (`token`), + UNIQUE KEY `idx_users_external_id` (`external_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +LOCK TABLES `directus_users` WRITE; +/*!40000 ALTER TABLE `directus_users` DISABLE KEYS */; + +INSERT INTO `directus_users` (`id`, `status`, `first_name`, `last_name`, `email`, `email_notifications`, `password`, `avatar`, `company`, `title`, `locale`, `high_contrast_mode`, `locale_options`, `timezone`, `last_ip`, `last_login`, `last_access`, `last_page`, `token`, `invite_token`, `invite_accepted`, `external_id`) +VALUES + (1,1,'Admin','User','admin@example.com',1,'$2y$12$MLltm.JyJ9ozdzDLjwJygeBypXvtwsTjXVIX/VbQS/9QE6Hx0dbdm',NULL,NULL,NULL,'en-US',0,NULL,'America/New_York',NULL,NULL,NULL,NULL,'admin_token',NULL,0,NULL); + +/*!40000 ALTER TABLE `directus_users` ENABLE KEYS */; +UNLOCK TABLES; + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/src/services/.gitkeep b/src/services/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/web.php b/src/web.php new file mode 100644 index 0000000000..90c7666420 --- /dev/null +++ b/src/web.php @@ -0,0 +1,178 @@ + [ + 'error' => 8, + 'message' => 'API Environment Configuration Not Found: ' . $env + ] + ]); + exit; + } +} + +$app = create_app($basePath, require $configFilePath); + +// ---------------------------------------------------------------------------- +// + +// ============================================================================= +// Error reporting +// ----------------------------------------------------------------------------- +// Possible values: +// +// 'production' => error suppression +// 'development' => no error suppression +// 'staging' => no error suppression +// +// ============================================================================= + +$errorReporting = E_ALL; +$displayErrors = 1; +if ($app->getConfig()->get('app.env', 'development') === 'production') { + $displayErrors = $errorReporting = 0; +} + +error_reporting($errorReporting); +ini_set('display_errors', $displayErrors); + +// ============================================================================= +// Timezone +// ============================================================================= +date_default_timezone_set($app->getConfig()->get('timezone', 'America/New_York')); + +$container = $app->getContainer(); + +register_global_hooks($app); +register_extensions_hooks($app); + +$app->getContainer()->get('hook_emitter')->run('application.boot', $app); + +// TODO: Implement old Slim 2 hooks into middleware + +// +// ---------------------------------------------------------------------------- + +$app->add(new \Directus\Application\Http\Middleware\TableGatewayMiddleware($app->getContainer())) + ->add(new \Directus\Application\Http\Middleware\IpRateLimitMiddleware($app->getContainer())) + ->add(new RKA\Middleware\IpAddress()) + ->add(new \Directus\Application\Http\Middleware\CorsMiddleware($app->getContainer())); + +$app->get('/', \Directus\Api\Routes\Home::class) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($app->getContainer())); + +$app->group('/{env}', function () { + $this->group('/activity', \Directus\Api\Routes\Activity::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/auth', \Directus\Api\Routes\Auth::class); + $this->group('/fields', \Directus\Api\Routes\Fields::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/files', \Directus\Api\Routes\Files::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/items', \Directus\Api\Routes\Items::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/collection_presets', \Directus\Api\Routes\CollectionPresets::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/permissions', \Directus\Api\Routes\Permissions::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/relations', \Directus\Api\Routes\Relations::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/revisions', \Directus\Api\Routes\Revisions::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/roles', \Directus\Api\Routes\Roles::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/settings', \Directus\Api\Routes\Settings::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/collections', \Directus\Api\Routes\Collections::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/users', \Directus\Api\Routes\Users::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + $this->group('/scim', function () { + $this->group('/v2', \Directus\Api\Routes\ScimTwo::class); + })->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer()));; + $this->group('/utils', \Directus\Api\Routes\Utils::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + + $this->group('/custom', function () { + $endpointsList = get_custom_endpoints('/public/custom/endpoints'); + + foreach ($endpointsList as $name => $endpoints) { + create_group_route_from_array($this, $name, $endpoints); + } + }); + + $this->group('/pages', function () { + $endpointsList = get_custom_endpoints('public/extensions/core/pages', true); + + foreach ($endpointsList as $name => $endpoints) { + create_group_route_from_array($this, $name, $endpoints); + } + }) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); + + $this->group('/interfaces', function () { + $endpointsList = get_custom_endpoints('public/extensions/core/interfaces', true); + + foreach ($endpointsList as $name => $endpoints) { + create_group_route_from_array($this, $name, $endpoints); + } + }) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($this->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($this->getContainer())); +}); + +$app->group('/interfaces', \Directus\Api\Routes\Interfaces::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($app->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($app->getContainer())); +$app->group('/listings', \Directus\Api\Routes\Listings::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($app->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($app->getContainer())); +$app->group('/pages', \Directus\Api\Routes\Pages::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($app->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($app->getContainer())); +$app->group('/server', \Directus\Api\Routes\Server::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($app->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($app->getContainer())); +$app->group('/types', \Directus\Api\Routes\Types::class) + ->add(new \Directus\Application\Http\Middleware\UserRateLimitMiddleware($app->getContainer())) + ->add(new \Directus\Application\Http\Middleware\AuthenticationMiddleware($app->getContainer())); + +$app->add(new \Directus\Application\Http\Middleware\ResponseCacheMiddleware($app->getContainer())); + +return $app; diff --git a/vendor/akrabat/rka-ip-address-middleware/.travis.yml b/vendor/akrabat/rka-ip-address-middleware/.travis.yml new file mode 100644 index 0000000000..f6408f316b --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/.travis.yml @@ -0,0 +1,19 @@ +sudo: false + +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + +before_install: + - composer self-update + +install: + - travis_retry composer install --no-interaction --ignore-platform-reqs --prefer-source + - composer info -i + +script: + - vendor/bin/phpcs + - vendor/bin/phpunit diff --git a/vendor/akrabat/rka-ip-address-middleware/LICENSE b/vendor/akrabat/rka-ip-address-middleware/LICENSE new file mode 100644 index 0000000000..1f8a265589 --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2015, Rob Allen (rob@akrabat.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * The name of Rob Allen may not be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/akrabat/rka-ip-address-middleware/README.md b/vendor/akrabat/rka-ip-address-middleware/README.md new file mode 100644 index 0000000000..3bb3856080 --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/README.md @@ -0,0 +1,45 @@ +# Client IP address middleware + +PSR-7 Middleware that determines the client IP address and stores it as an `ServerRequest` attribute called `ip_address`. + +[![Build status][Master image]][Master] + + +This middleware checks the 'X-Forwarded-For', 'X-Forwarded', 'X-Cluster-Client-Ip', 'Client-Ip' headers for the first IP address it can find. If none of the headers exist, or do not have a valid IP address, then the `$_SERVER['REMOTE_ADDR']` is used. + +*Note that the proxy headers are only checked if the first parameter to the constructor is set to `true`. If set to false, then only `$_SERVER['REMOTE_ADDR']` is used* + +**Trusted Proxies** + +You can set a list of proxies that are trusted as the second constructor parameter. If this list is set, then the proxy headers will only be checked if the `REMOTE_ADDR` is in the trusted list. + + +## Installation + +`composer require akrabat/rka-ip-address-middleware` + +## Usage + +In Slim 3: + +```php +$checkProxyHeaders = true; // Note: Never trust the IP address for security processes! +$trustedProxies = ['10.0.0.1', '10.0.0.2']; // Note: Never trust the IP address for security processes! +$app->add(new RKA\Middleware\IpAddress($checkProxyHeaders, $trustedProxies)); + +$app->get('/', function ($request, $response, $args) { + $ipAddress = $request->getAttribute('ip_address'); + + return $response; +}); +``` + +## Testing + +* Code coverage: ``$ vendor/bin/phpcs`` +* Unit tests: ``$ vendor/bin/phpunit`` +* Code coverage: ``$ vendor/bin/phpunit --coverage-html ./build`` + + +[Master]: https://travis-ci.org/akrabat/rka-content-type-renderer +[Master image]: https://secure.travis-ci.org/akrabat/rka-content-type-renderer.svg?branch=master diff --git a/vendor/akrabat/rka-ip-address-middleware/composer.json b/vendor/akrabat/rka-ip-address-middleware/composer.json new file mode 100644 index 0000000000..fc8e5218c4 --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/composer.json @@ -0,0 +1,30 @@ +{ + "name": "akrabat/rka-ip-address-middleware", + "description": "PSR-7 Middleware that determines the client IP address and stores it as an ServerRequest attribute", + "keywords": [ + "psr7", "middleware", "ip" + ], + "homepage": "http://github.com/akrabat/rka-ip-address-middleware", + "type": "library", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + } + ], + "require": { + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "squizlabs/php_codesniffer": "^2.3", + "zendframework/zend-diactoros": "^1.1" + }, + "autoload": { + "psr-4": { + "RKA\\Middleware\\": "src" + } + } +} diff --git a/vendor/akrabat/rka-ip-address-middleware/phpcs.xml b/vendor/akrabat/rka-ip-address-middleware/phpcs.xml new file mode 100644 index 0000000000..baa2623219 --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/phpcs.xml @@ -0,0 +1,9 @@ + + + + + + src + tests + + diff --git a/vendor/akrabat/rka-ip-address-middleware/phpunit.xml b/vendor/akrabat/rka-ip-address-middleware/phpunit.xml new file mode 100644 index 0000000000..fb5fbdc1ab --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/phpunit.xml @@ -0,0 +1,28 @@ + + + + + + ./tests/ + + + + + ./src + + + \ No newline at end of file diff --git a/vendor/akrabat/rka-ip-address-middleware/src/IpAddress.php b/vendor/akrabat/rka-ip-address-middleware/src/IpAddress.php new file mode 100644 index 0000000000..1d632d7e16 --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/src/IpAddress.php @@ -0,0 +1,172 @@ +checkProxyHeaders = $checkProxyHeaders; + $this->trustedProxies = $trustedProxies; + + if ($attributeName) { + $this->attributeName = $attributeName; + } + if (!empty($headersToInspect)) { + $this->headersToInspect = $headersToInspect; + } + } + + /** + * Set the "$attributeName" attribute to the client's IP address as determined from + * the proxy header (X-Forwarded-For or from $_SERVER['REMOTE_ADDR'] + * + * @param ServerRequestInterface $request PSR7 request + * @param ResponseInterface $response PSR7 response + * @param callable $next Next middleware + * + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) + { + if (!$next) { + return $response; + } + + $ipAddress = $this->determineClientIpAddress($request); + $request = $request->withAttribute($this->attributeName, $ipAddress); + + return $response = $next($request, $response); + } + + /** + * Find out the client's IP address from the headers available to us + * + * @param ServerRequestInterface $request PSR-7 Request + * @return string + */ + protected function determineClientIpAddress($request) + { + $ipAddress = null; + + $serverParams = $request->getServerParams(); + if (isset($serverParams['REMOTE_ADDR']) && $this->isValidIpAddress($serverParams['REMOTE_ADDR'])) { + $ipAddress = $serverParams['REMOTE_ADDR']; + } + + $checkProxyHeaders = $this->checkProxyHeaders; + if ($checkProxyHeaders && !empty($this->trustedProxies)) { + if (!in_array($ipAddress, $this->trustedProxies)) { + $checkProxyHeaders = false; + } + } + + if ($checkProxyHeaders) { + foreach ($this->headersToInspect as $header) { + if ($request->hasHeader($header)) { + $ip = $this->getFirstIpAddressFromHeader($request, $header); + if ($this->isValidIpAddress($ip)) { + $ipAddress = $ip; + break; + } + } + } + } + + return $ipAddress; + } + + /** + * Check that a given string is a valid IP address + * + * @param string $ip + * @return boolean + */ + protected function isValidIpAddress($ip) + { + $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6; + if (filter_var($ip, FILTER_VALIDATE_IP, $flags) === false) { + return false; + } + return true; + } + + /** + * Find out the client's IP address from the headers available to us + * + * @param ServerRequestInterface $request PSR-7 Request + * @param string $header Header name + * @return string + */ + private function getFirstIpAddressFromHeader($request, $header) + { + $items = explode(',', $request->getHeaderLine($header)); + $headerValue = trim(reset($items)); + + if (ucfirst($header) == 'Forwarded') { + foreach (explode(';', $headerValue) as $headerPart) { + if (strtolower(substr($headerPart, 0, 4)) == 'for=') { + $for = explode(']', $headerPart); + $headerValue = trim(substr(reset($for), 4), " \t\n\r\0\x0B" . "\"[]"); + break; + } + } + } + + return $headerValue; + } +} diff --git a/vendor/akrabat/rka-ip-address-middleware/tests/IpAddressTest.php b/vendor/akrabat/rka-ip-address-middleware/tests/IpAddressTest.php new file mode 100644 index 0000000000..decd557c8e --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/tests/IpAddressTest.php @@ -0,0 +1,271 @@ + '192.168.1.1', + ]); + $response = new Response(); + + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('IP'); + return $response; + }); + + $this->assertSame('192.168.1.1', $ipAddress); + } + + public function testIpIsNullIfMissing() + { + $middleware = new IPAddress(); + + $request = ServerRequestFactory::fromGlobals(); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertNull($ipAddress); + } + + public function testXForwardedForIp() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_X_FORWARDED_FOR' => '192.168.1.3, 192.168.1.2, 192.168.1.1' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.1.3', $ipAddress); + } + + public function testProxyIpIsIgnored() + { + $middleware = new IPAddress(); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.0.1', + 'HTTP_X_FORWARDED_FOR' => '192.168.1.3, 192.168.1.2, 192.168.1.1' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.0.1', $ipAddress); + } + + public function testHttpClientIp() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_CLIENT_IP' => '192.168.1.3' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.1.3', $ipAddress); + } + + public function testXForwardedForIpV6() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_X_FORWARDED_FOR' => '001:DB8::21f:5bff:febf:ce22:8a2e' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('001:DB8::21f:5bff:febf:ce22:8a2e', $ipAddress); + } + + public function testXForwardedForWithInvalidIp() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_X_FORWARDED_FOR' => 'foo-bar' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.1.1', $ipAddress); + } + + public function testXForwardedForIpWithTrustedProxy() + { + $middleware = new IPAddress(true, ['192.168.0.1', '192.168.0.2']); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.0.2', + 'HTTP_X_FORWARDED_FOR' => '192.168.1.3, 192.168.1.2, 192.168.1.1' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.1.3', $ipAddress); + } + + public function testXForwardedForIpWithUntrustedProxy() + { + $middleware = new IPAddress(true, ['192.168.0.1']); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.0.2', + 'HTTP_X_FORWARDED_FOR' => '192.168.1.3, 192.168.1.2, 192.168.1.1' + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.0.2', $ipAddress); + } + + public function testForwardedWithMultipleFor() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_FORWARDED' => 'for=192.0.2.43, for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com', + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.0.2.43', $ipAddress); + } + + public function testForwardedWithAllOptions() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_FORWARDED' => 'for=192.0.2.60; proto=http;by=203.0.113.43; host=_hiddenProxy, for=192.0.2.61', + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.0.2.60', $ipAddress); + } + + public function testForwardedWithWithIpV6() + { + $middleware = new IPAddress(true); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.1.1', + 'HTTP_FORWARDED' => 'For="[2001:db8:cafe::17]:4711", for=_internalProxy', + ]); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('2001:db8:cafe::17', $ipAddress); + } + + public function testCustomHeader() + { + $headersToInspect = [ + 'Foo-Bar' + ]; + $middleware = new IPAddress(true, [], null, $headersToInspect); + + $request = ServerRequestFactory::fromGlobals([ + 'REMOTE_ADDR' => '192.168.0.1', + ]); + $request = $request->withAddedHeader('Foo-Bar', '192.168.1.3'); + $response = new Response(); + + $ipAddress = '123'; + $response = $middleware($request, $response, function ($request, $response) use (&$ipAddress) { + // simply store the "ip_address" attribute in to the referenced $ipAddress + $ipAddress = $request->getAttribute('ip_address'); + return $response; + }); + + $this->assertSame('192.168.1.3', $ipAddress); + } +} diff --git a/vendor/akrabat/rka-ip-address-middleware/tests/bootstrap.php b/vendor/akrabat/rka-ip-address-middleware/tests/bootstrap.php new file mode 100644 index 0000000000..a4ae4bea2a --- /dev/null +++ b/vendor/akrabat/rka-ip-address-middleware/tests/bootstrap.php @@ -0,0 +1,5 @@ +addPsr4('RKA\\Middleware\\Test\\', __DIR__); diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000000..dfc0577d5d --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ +&1 && + echo "########################################" && + echo "########################################" && + echo -e "\\e[32mOK\\e[0m $title\\n\\ntravis_fold:end:$fold" || + ( echo -e "\\e[41mKO\\e[0m $title\\n" && exit 1 ) + } + export -f tfold + + COMPONENTS=$(find src -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n') + + # php.ini configuration + INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini + echo memory_limit = -1 >> $INI + +install: + - if [[ ! $skip ]]; then $COMPOSER_UP; fi + - | + run_tests () { + set -e + if [[ $skip ]]; then + echo -e "\\n\\e[1;34mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m" + elif [[ $deps = high ]]; then + echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" + elif [[ $deps = low ]]; then + sleep 3 + echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP --prefer-lowest --prefer-stable && $PHPUNIT_X'" + elif [[ $COVERAGE == true ]]; then + $PHPUNIT_COVERAGE + else + echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" + tfold tty-group $PHPUNIT --group tty + fi + } + +script: + - (run_tests) + +after_success: + - if [[ $COVERAGE == true ]]; then pip install --user codecov && codecov ; fi + +notifications: + email: false + webhooks: + urls: + - https://webhooks.gitter.im/e/7b0035a70de31fa976df diff --git a/vendor/cache/cache/CONTRIBUTING.md b/vendor/cache/cache/CONTRIBUTING.md new file mode 100644 index 0000000000..5bc36e55d6 --- /dev/null +++ b/vendor/cache/cache/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contribute to PHP-Cache* + +Thank you for contributing to PHP-Cache! + +Before we can merge your Pull-Request here are some guidelines that you need to follow. +These guidelines exist not to annoy you, but to keep the code base clean, +unified and future proof. + +## We only accept PRs to "master" + +Our branching strategy is "everything to master first", even +bugfixes and we then merge them into the stable branches. You should only +open pull requests against the master branch. Otherwise we cannot accept the PR. + +There is one exception to the rule, when we merged a bug into some stable branches +we do occasionally accept pull requests that merge the same bug fix into earlier +branches. + +## Coding Standard + +We use PSR-1 and PSR-2: + +* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md + +with some exceptions/differences: + +* Keep the nesting of control structures per method as small as possible +* Align equals (=) signs +* Prefer early exit over nesting conditions + +All pull requests will end up getting run through Style CI, which is required to pass. + +## Unit-Tests + +Please try to add a test for your pull-request. + +* If your test is specific for an adapter/library, put it in the library tests. +* If it is adapter agnostic, put it in the src/IntegrationTests directory + +You can run the unit-tests by calling `composer test` from the root of the project. +It will run all the tests for each library. + +In order to do that, you will need a fresh copy of php-cache/cache, and you +will have to run a composer installation in the project: + +```sh +git clone git@github.com:php-cache/cache.git +cd cache +curl -sS https://getcomposer.org/installer | php -- +./composer.phar install +``` + +## Travis + +We automatically run your pull request through [Travis CI](http://www.travis-ci.org) +against on all of the adapters. If you break the tests, we cannot merge your code, +so please make sure that your code is working before opening up a Pull-Request. + +## Getting merged + +Please allow us time to review your pull requests. We will give our best to review +everything as fast as possible, but cannot always live up to our own expectations. + +Thank you very much again for your contribution! + +\* Any similarities to the Doctrine contributing file is NOT coincidence. We've used their CONTRIBUTING.md file as a basis for ours :) + diff --git a/vendor/cache/cache/LICENSE b/vendor/cache/cache/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/README.md b/vendor/cache/cache/README.md new file mode 100644 index 0000000000..e8c7ef2440 --- /dev/null +++ b/vendor/cache/cache/README.md @@ -0,0 +1,20 @@ +# PHP Cache + +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![codecov.io](https://codecov.io/github/php-cache/cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/cache?branch=master) +[![Build Status](https://travis-ci.org/php-cache/cache.svg?branch=master)](https://travis-ci.org/php-cache/cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![StyleCI](https://styleci.io/repos/50789153/shield)](https://styleci.io/repos/50789153) + +This is the main repository for all our cache adapters and libraries. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Notice + +This library is for development use. All pull requests to the adapters, and libraries included in here, should be made to this library. + +If you are lazy, you can also include this project, to include all the adapters and libraries. + +### Contribute + +Contributions are very welcome! Make sure you check out the [contributing docs](CONTRIBUTING.md) and send us a pull request or report any issues you find on the [issue tracker](http://issues.php-cache.com). Feel free to come chat with us on [Gitter](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) too. diff --git a/vendor/cache/cache/build/README.md b/vendor/cache/cache/build/README.md new file mode 100755 index 0000000000..5637a313e7 --- /dev/null +++ b/vendor/cache/cache/build/README.md @@ -0,0 +1,7 @@ +# Build Scripts + +This directory contains the scripts that travis uses to build the project. + +The scripts in [the php directory](php/) are ran when travis is testing the given version and adapter. [`all.sh`](php/all.sh) is ran for all adapters but for one php version. + +Tests are ran using [`runTest.sh`](runTest.sh). This file changes the directory to the library subdirectory, installs composer, and runs the tests. diff --git a/vendor/cache/cache/build/php/5.6/Apc.sh b/vendor/cache/cache/build/php/5.6/Apc.sh new file mode 100755 index 0000000000..c4a15ef54c --- /dev/null +++ b/vendor/cache/cache/build/php/5.6/Apc.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Add php.ini settings" +phpenv config-add ./build/php/apc.ini + +echo "Install APC Adapter & APCu Adapter dependencies" +yes '' | pecl install -f apcu-4.0.11 diff --git a/vendor/cache/cache/build/php/5.6/Apcu.sh b/vendor/cache/cache/build/php/5.6/Apcu.sh new file mode 100755 index 0000000000..dbc71fd656 --- /dev/null +++ b/vendor/cache/cache/build/php/5.6/Apcu.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Add php.ini settings" +phpenv config-add ./build/php/apc.ini + +echo "Install APC Adapter & APCu Adapter dependencies" +yes '' | pecl install -f apcu-4.0.10 diff --git a/vendor/cache/cache/build/php/5.6/Memcache.sh b/vendor/cache/cache/build/php/5.6/Memcache.sh new file mode 100755 index 0000000000..eb60e26a24 --- /dev/null +++ b/vendor/cache/cache/build/php/5.6/Memcache.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Add php.ini settings" +phpenv config-add ./build/php/memcache.ini + +echo "Enable extension" +echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/vendor/cache/cache/build/php/5.6/Memcached.sh b/vendor/cache/cache/build/php/5.6/Memcached.sh new file mode 100755 index 0000000000..5c7ff622a7 --- /dev/null +++ b/vendor/cache/cache/build/php/5.6/Memcached.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Enable extension" +echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/vendor/cache/cache/build/php/5.6/MongoDB.sh b/vendor/cache/cache/build/php/5.6/MongoDB.sh new file mode 100755 index 0000000000..5a58535c84 --- /dev/null +++ b/vendor/cache/cache/build/php/5.6/MongoDB.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +pecl install -f mongodb-1.1.2 + diff --git a/vendor/cache/cache/build/php/5.6/Redis.sh b/vendor/cache/cache/build/php/5.6/Redis.sh new file mode 100755 index 0000000000..3589d6eaef --- /dev/null +++ b/vendor/cache/cache/build/php/5.6/Redis.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Install redis" +yes '' | pecl install -f redis-2.2.8 + +#echo "Enable extension" +#echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini diff --git a/vendor/cache/cache/build/php/7.0/Apcu.sh b/vendor/cache/cache/build/php/7.0/Apcu.sh new file mode 100755 index 0000000000..319d2aaf36 --- /dev/null +++ b/vendor/cache/cache/build/php/7.0/Apcu.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Add php.ini settings" +phpenv config-add ./build/php/apc.ini + +echo "Install APCu Adapter dependencies" +yes '' | pecl install -f apcu-5.1.8 diff --git a/vendor/cache/cache/build/php/7.0/Memcached.sh b/vendor/cache/cache/build/php/7.0/Memcached.sh new file mode 100755 index 0000000000..9577aa037b --- /dev/null +++ b/vendor/cache/cache/build/php/7.0/Memcached.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Install memcache(d)" +yes '' | pecl install memcached diff --git a/vendor/cache/cache/build/php/7.0/MongoDB.sh b/vendor/cache/cache/build/php/7.0/MongoDB.sh new file mode 100755 index 0000000000..b690595749 --- /dev/null +++ b/vendor/cache/cache/build/php/7.0/MongoDB.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +echo "Install MongoDB" +pecl install -f mongodb-1.1.2 + diff --git a/vendor/cache/cache/build/php/7.0/Redis.sh b/vendor/cache/cache/build/php/7.0/Redis.sh new file mode 100755 index 0000000000..ace1417615 --- /dev/null +++ b/vendor/cache/cache/build/php/7.0/Redis.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Install Redis" +yes '' | pecl install -f redis-3.0.0 diff --git a/vendor/cache/cache/build/php/7.1/Apcu.sh b/vendor/cache/cache/build/php/7.1/Apcu.sh new file mode 100755 index 0000000000..319d2aaf36 --- /dev/null +++ b/vendor/cache/cache/build/php/7.1/Apcu.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Add php.ini settings" +phpenv config-add ./build/php/apc.ini + +echo "Install APCu Adapter dependencies" +yes '' | pecl install -f apcu-5.1.8 diff --git a/vendor/cache/cache/build/php/7.1/Memcached.sh b/vendor/cache/cache/build/php/7.1/Memcached.sh new file mode 100755 index 0000000000..9577aa037b --- /dev/null +++ b/vendor/cache/cache/build/php/7.1/Memcached.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Install memcache(d)" +yes '' | pecl install memcached diff --git a/vendor/cache/cache/build/php/7.1/MongoDB.sh b/vendor/cache/cache/build/php/7.1/MongoDB.sh new file mode 100755 index 0000000000..103bcf7eb8 --- /dev/null +++ b/vendor/cache/cache/build/php/7.1/MongoDB.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Install MongoDB" +pecl install -f mongodb-1.1.2 diff --git a/vendor/cache/cache/build/php/7.1/Redis.sh b/vendor/cache/cache/build/php/7.1/Redis.sh new file mode 100755 index 0000000000..65ca9bdd43 --- /dev/null +++ b/vendor/cache/cache/build/php/7.1/Redis.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Install redis" +yes '' | pecl install -f redis-3.0.0 diff --git a/vendor/cache/cache/build/php/all.sh b/vendor/cache/cache/build/php/all.sh new file mode 100755 index 0000000000..4aeb861cec --- /dev/null +++ b/vendor/cache/cache/build/php/all.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +for SCRIPT in build/php/$TRAVIS_PHP_VERSION/*.sh +do + echo "Found Script: $SCRIPT" + if [ -f $SCRIPT -a -x $SCRIPT ] + then + echo "Running: $SCRIPT" + . $SCRIPT + fi +done \ No newline at end of file diff --git a/vendor/cache/cache/build/php/apc.ini b/vendor/cache/cache/build/php/apc.ini new file mode 100755 index 0000000000..d85888ee61 --- /dev/null +++ b/vendor/cache/cache/build/php/apc.ini @@ -0,0 +1,3 @@ +apc.enabled=1 +apc.enable_cli=1 +apc.use_request_time=0 \ No newline at end of file diff --git a/vendor/cache/cache/build/php/apcu_bc.ini b/vendor/cache/cache/build/php/apcu_bc.ini new file mode 100644 index 0000000000..3ff19e7d78 --- /dev/null +++ b/vendor/cache/cache/build/php/apcu_bc.ini @@ -0,0 +1 @@ +extension=apc.so \ No newline at end of file diff --git a/vendor/cache/cache/build/php/memcache.ini b/vendor/cache/cache/build/php/memcache.ini new file mode 100755 index 0000000000..c23acc3be8 --- /dev/null +++ b/vendor/cache/cache/build/php/memcache.ini @@ -0,0 +1,2 @@ +memcache.enabled=1 +memcache.enable_cli=1 \ No newline at end of file diff --git a/vendor/cache/cache/composer.json b/vendor/cache/cache/composer.json new file mode 100644 index 0000000000..6b255d2bf4 --- /dev/null +++ b/vendor/cache/cache/composer.json @@ -0,0 +1,79 @@ +{ + "name": "cache/cache", + "type": "library", + "description": "Library of all the php-cache adapters", + "keywords": [ + "cache", + "psr6" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/log": "^1.0", + "doctrine/cache": "^1.3", + "psr/simple-cache": "^1.0", + "league/flysystem": "^1.0" + }, + "require-dev": { + "defuse/php-encryption": "^2.0", + "phpunit/phpunit": "^5.7.21", + "mockery/mockery": "^0.9.9", + "cache/integration-tests": "^0.16", + "predis/predis": "^1.1", + "symfony/cache": "^3.1", + "illuminate/cache": "^5.4" + }, + "suggest": { + "ext-apc": "APC extension is required to use the APC Adapter", + "ext-apcu": "APCu extension is required to use the APCu Adapter", + "ext-memcache": "Memcache extension is required to use the Memcache Adapter", + "ext-memcached": "Memcached extension is required to use the Memcached Adapter", + "ext-redis": "Redis extension is required to use the Redis adapter", + "ext-mongodb": "Mongodb extension required to use the Mongodb adapter", + "mongodb/mongodb": "Mongodb lib required to use the Mongodb adapter" + }, + "conflict": { + "cache/apc-adapter": "*", + "cache/apcu-adapter": "*", + "cache/chain-adapter": "*", + "cache/adapter-common": "*", + "cache/doctrine-adapter": "*", + "cache/filesystem-adapter": "*", + "cache/memcache-adapter": "*", + "cache/memcached-adapter": "*", + "cache/mongodb-adapter": "*", + "cache/array-adapter": "*", + "cache/predis-adapter": "*", + "cache/redis-adapter": "*", + "cache/void-adapter": "*", + "cache/psr-6-doctrine-bridge": "*", + "cache/hierarchical-cache": "*", + "cache/session-handler": "*", + "cache/taggable-cache": "*", + "cache/illuminate-adapter": "*" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "Cache\\": "src/" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + } +} diff --git a/vendor/cache/cache/phpunit b/vendor/cache/cache/phpunit new file mode 100755 index 0000000000..14194b1bc6 --- /dev/null +++ b/vendor/cache/cache/phpunit @@ -0,0 +1,9 @@ +#!/usr/bin/env php + + + + + + ./src/*/Tests + ./src/*/*/Tests + + + + + + + + + + + + ./ + + ./src/*/Tests + ./src/*/*/Tests + ./vendor + + + + diff --git a/vendor/cache/cache/script/remove-vendor-dirs.sh b/vendor/cache/cache/script/remove-vendor-dirs.sh new file mode 100755 index 0000000000..9c6af13afd --- /dev/null +++ b/vendor/cache/cache/script/remove-vendor-dirs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Remove all vendor dirs +ROOT=$(pwd) + +# Run for each components +find src -mindepth 2 -type f -name composer.json | while read line; do + # Save the directory name + DIR=$(dirname $line) + + if [[ ! -z $DIR ]]; then + # Remove vendors + rm -rf "$DIR/vendor" + fi + +done diff --git a/vendor/cache/cache/script/update-integration-tests-version.sh b/vendor/cache/cache/script/update-integration-tests-version.sh new file mode 100755 index 0000000000..f8b0451665 --- /dev/null +++ b/vendor/cache/cache/script/update-integration-tests-version.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +if [ $# -eq 0 ] +then + echo "No arguments supplied. You need to specify the version to composer." + exit 1 +fi + +VERSION=$1 +ROOT=$(pwd) + +# Run for each components +find src -mindepth 2 -type f -name phpunit.xml.dist | while read line; do + # Save the directory name + DIR=$(dirname $line) + + # Go to that directory + cd $DIR + pwd + + if [[ "$DIR" != "src/Bridge/Doctrine" && "$DIR" != "src/Hierarchy" && "$DIR" != "src/Namespaced" && "$DIR" != "src/SessionHandler" ]]; then + # Let composer update the composer.json + composer require --dev --no-update cache/integration-tests:$VERSION + fi + + # Go back to the root + cd $ROOT + echo "" +done + +# Update integration test for the root +pwd +composer require --dev --no-update cache/integration-tests:$VERSION diff --git a/vendor/cache/cache/src/Adapter/Apc/ApcCachePool.php b/vendor/cache/cache/src/Adapter/Apc/ApcCachePool.php new file mode 100644 index 0000000000..36ad518867 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apc/ApcCachePool.php @@ -0,0 +1,116 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Apc; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Adapter\Common\TagSupportWithArray; + +/** + * @author Tobias Nyholm + */ +class ApcCachePool extends AbstractCachePool +{ + use TagSupportWithArray; + + /** + * @type bool + */ + private $skipOnCli; + + /** + * @param bool $skipOnCli + */ + public function __construct($skipOnCli = false) + { + $this->skipOnCli = $skipOnCli; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if ($this->skipIfCli()) { + return [false, null, [], null]; + } + + $success = false; + $cacheData = apc_fetch($key, $success); + if (!$success) { + return [false, null, [], null]; + } + list($data, $tags, $timestamp) = unserialize($cacheData); + + return [$success, $data, $tags, $timestamp]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return apc_clear_cache('user'); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + apc_delete($key); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + if ($this->skipIfCli()) { + return false; + } + + if ($ttl < 0) { + return false; + } + + return apc_store($item->getKey(), serialize([$item->get(), $item->getTags(), $item->getExpirationTimestamp()]), $ttl); + } + + /** + * Returns true if CLI and if it should skip on cli. + * + * @return bool + */ + private function skipIfCli() + { + return $this->skipOnCli && php_sapi_name() === 'cli'; + } + + /** + * {@inheritdoc} + */ + public function getDirectValue($name) + { + return apc_fetch($name); + } + + /** + * {@inheritdoc} + */ + public function setDirectValue($name, $value) + { + apc_store($name, $value); + } +} diff --git a/vendor/cache/cache/src/Adapter/Apc/Changelog.md b/vendor/cache/cache/src/Adapter/Apc/Changelog.md new file mode 100644 index 0000000000..03a97a4e6f --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apc/Changelog.md @@ -0,0 +1,36 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.4.0 + +## 0.4.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.3.2 + +### Added + +* You may configure the adapter to be disabled on CLI + +## 0.3.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Apc/LICENSE b/vendor/cache/cache/src/Adapter/Apc/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apc/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Apc/README.md b/vendor/cache/cache/src/Adapter/Apc/README.md new file mode 100644 index 0000000000..d92a142152 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apc/README.md @@ -0,0 +1,29 @@ +# Apc PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/apc-adapter/v/stable)](https://packagist.org/packages/cache/apc-adapter) +[![codecov.io](https://codecov.io/github/php-cache/apc-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/apc-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/apc-adapter/downloads)](https://packagist.org/packages/cache/apc-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/apc-adapter/d/monthly.png)](https://packagist.org/packages/cache/apc-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Apc. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/apc-adapter +``` + +### Use + +You do not need to do any configuration to use the `ApcCachePool`. + +```php +$pool = new ApcCachePool(); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Apc/composer.json b/vendor/cache/cache/src/Adapter/Apc/composer.json new file mode 100644 index 0000000000..f19e515759 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apc/composer.json @@ -0,0 +1,55 @@ +{ + "name": "cache/apc-adapter", + "description": "A PSR-6 cache implementation using apc. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "apc" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0" + }, + "suggest": { + "ext-apc": "The extension required to use this pool." + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Apc\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Apcu/ApcuCachePool.php b/vendor/cache/cache/src/Adapter/Apcu/ApcuCachePool.php new file mode 100644 index 0000000000..de11f580ab --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apcu/ApcuCachePool.php @@ -0,0 +1,116 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Apcu; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Adapter\Common\TagSupportWithArray; + +/** + * @author Tobias Nyholm + */ +class ApcuCachePool extends AbstractCachePool +{ + use TagSupportWithArray; + + /** + * @type bool + */ + private $skipOnCli; + + /** + * @param bool $skipOnCli + */ + public function __construct($skipOnCli = false) + { + $this->skipOnCli = $skipOnCli; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if ($this->skipIfCli()) { + return [false, null, [], null]; + } + + $success = false; + $cacheData = apcu_fetch($key, $success); + if (!$success) { + return [false, null, [], null]; + } + list($data, $tags, $timestamp) = unserialize($cacheData); + + return [$success, $data, $tags, $timestamp]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + apcu_delete($key); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + if ($this->skipIfCli()) { + return false; + } + + if ($ttl < 0) { + return false; + } + + return apcu_store($item->getKey(), serialize([$item->get(), $item->getTags(), $item->getExpirationTimestamp()]), $ttl); + } + + /** + * Returns true if CLI and if it should skip on cli. + * + * @return bool + */ + private function skipIfCli() + { + return $this->skipOnCli && php_sapi_name() === 'cli'; + } + + /** + * {@inheritdoc} + */ + public function getDirectValue($name) + { + return apcu_fetch($name); + } + + /** + * {@inheritdoc} + */ + public function setDirectValue($name, $value) + { + apcu_store($name, $value); + } +} diff --git a/vendor/cache/cache/src/Adapter/Apcu/Changelog.md b/vendor/cache/cache/src/Adapter/Apcu/Changelog.md new file mode 100644 index 0000000000..4f778cd47b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apcu/Changelog.md @@ -0,0 +1,37 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.3.0 + +## 0.3.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` +* Dropped PHP 5.5 support + +## 0.2.2 + +### Added + +* You may configure the adapter to be disabled on CLI + +## 0.2.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Apcu/LICENSE b/vendor/cache/cache/src/Adapter/Apcu/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apcu/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Apcu/README.md b/vendor/cache/cache/src/Adapter/Apcu/README.md new file mode 100644 index 0000000000..fd031a1088 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apcu/README.md @@ -0,0 +1,29 @@ +# Apcu PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/apcu-adapter/v/stable)](https://packagist.org/packages/cache/apcu-adapter) +[![codecov.io](https://codecov.io/github/php-cache/apcu-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/apcu-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/apcu-adapter/downloads)](https://packagist.org/packages/cache/apcu-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/apcu-adapter/d/monthly.png)](https://packagist.org/packages/cache/apcu-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Apcu. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/apcu-adapter +``` + +### Use + +You do not need to do any configuration to use the `ApcuCachePool`. + +```php +$pool = new ApcuCachePool(); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Apcu/composer.json b/vendor/cache/cache/src/Adapter/Apcu/composer.json new file mode 100644 index 0000000000..d8c2783cb3 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Apcu/composer.json @@ -0,0 +1,55 @@ +{ + "name": "cache/apcu-adapter", + "description": "A PSR-6 cache implementation using apcu. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "apcu" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "suggest": { + "ext-apcu": "The extension required to use this pool." + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Apcu\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Chain/CachePoolChain.php b/vendor/cache/cache/src/Adapter/Chain/CachePoolChain.php new file mode 100644 index 0000000000..2d18db1202 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/CachePoolChain.php @@ -0,0 +1,353 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Chain; + +use Cache\Adapter\Chain\Exception\NoPoolAvailableException; +use Cache\Adapter\Chain\Exception\PoolFailedException; +use Cache\Adapter\Common\Exception\CachePoolException; +use Cache\TagInterop\TaggableCacheItemPoolInterface; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; + +/** + * @author Tobias Nyholm + */ +class CachePoolChain implements CacheItemPoolInterface, TaggableCacheItemPoolInterface, LoggerAwareInterface +{ + /** + * @type LoggerInterface + */ + private $logger; + + /** + * @type TaggableCacheItemPoolInterface[]|CacheItemPoolInterface[] + */ + private $pools; + + /** + * @type array + */ + private $options; + + /** + * @param array $pools + * @param array $options { + * + * @type bool $skip_on_failure If true we will remove a pool form the chain if it fails. + * } + */ + public function __construct(array $pools, array $options = []) + { + $this->pools = $pools; + + if (!isset($options['skip_on_failure'])) { + $options['skip_on_failure'] = false; + } + + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $found = false; + $result = null; + $item = null; + $needsSave = []; + + foreach ($this->getPools() as $poolKey => $pool) { + try { + $item = $pool->getItem($key); + + if ($item->isHit()) { + $found = true; + $result = $item; + break; + } + + $needsSave[] = $pool; + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + if ($found) { + foreach ($needsSave as $pool) { + $pool->save($result); + } + + $item = $result; + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $hits = []; + $loadedItems = []; + $notFoundItems = []; + $keysCount = count($keys); + foreach ($this->getPools() as $poolKey => $pool) { + try { + $items = $pool->getItems($keys); + + /** @type CacheItemInterface $item */ + foreach ($items as $item) { + if ($item->isHit()) { + $hits[$item->getKey()] = $item; + unset($keys[array_search($item->getKey(), $keys)]); + } else { + $notFoundItems[$poolKey][$item->getKey()] = $item->getKey(); + } + $loadedItems[$item->getKey()] = $item; + } + if (count($hits) === $keysCount) { + break; + } + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + if (!empty($hits) && !empty($notFoundItems)) { + foreach ($notFoundItems as $poolKey => $itemKeys) { + try { + $pool = $this->getPools()[$poolKey]; + $found = false; + foreach ($itemKeys as $itemKey) { + if (!empty($hits[$itemKey])) { + $found = true; + $pool->saveDeferred($hits[$itemKey]); + } + } + if ($found) { + $pool->commit(); + } + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + } + + return array_merge($loadedItems, $hits); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + foreach ($this->getPools() as $poolKey => $pool) { + try { + if ($pool->hasItem($key)) { + return true; + } + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $result && $pool->clear(); + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $result && $pool->deleteItem($key); + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $result && $pool->deleteItems($keys); + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $pool->save($item) && $result; + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $pool->saveDeferred($item) && $result; + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $pool->commit() && $result; + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function invalidateTag($tag) + { + return $this->invalidateTags([$tag]); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $result = true; + foreach ($this->getPools() as $poolKey => $pool) { + try { + $result = $pool->invalidateTags($tags) && $result; + } catch (CachePoolException $e) { + $this->handleException($poolKey, __FUNCTION__, $e); + } + } + + return $result; + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Logs with an arbitrary level if the logger exists. + * + * @param mixed $level + * @param string $message + * @param array $context + */ + protected function log($level, $message, array $context = []) + { + if ($this->logger !== null) { + $this->logger->log($level, $message, $context); + } + } + + /** + * @return array|\Psr\Cache\CacheItemPoolInterface[] + */ + protected function getPools() + { + if (empty($this->pools)) { + throw new NoPoolAvailableException('No valid cache pool available for the chain.'); + } + + return $this->pools; + } + + /** + * @param string $poolKey + * @param string $operation + * @param CachePoolException $exception + * + * @throws PoolFailedException + */ + private function handleException($poolKey, $operation, CachePoolException $exception) + { + if (!$this->options['skip_on_failure']) { + throw $exception; + } + + $this->log( + 'warning', + sprintf('Removing pool "%s" from chain because it threw an exception when executing "%s"', $poolKey, + $operation), + ['exception' => $exception] + ); + + unset($this->pools[$poolKey]); + } +} diff --git a/vendor/cache/cache/src/Adapter/Chain/Changelog.md b/vendor/cache/cache/src/Adapter/Chain/Changelog.md new file mode 100644 index 0000000000..4844c1100c --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/Changelog.md @@ -0,0 +1,38 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Added + +The `CachePoolChain` does now implement `Cache\TagInterop\TaggableCacheItemPoolInterface` + +### Removed + +- Removed deprecated function `clearTags` + +## 0.5.1 + +### Fixed + +* Fixed issue with generator +* Make sure `getItems` always save values back to previous pools + +## 0.5.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. + +## 0.4.0 + +### Changed + +* `CachePoolChain::getPools` is protected instead of public + +## 0.3.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Chain/Exception/NoPoolAvailableException.php b/vendor/cache/cache/src/Adapter/Chain/Exception/NoPoolAvailableException.php new file mode 100644 index 0000000000..3c66ba3a1e --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/Exception/NoPoolAvailableException.php @@ -0,0 +1,21 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Chain\Exception; + +use Cache\Adapter\Common\Exception\CachePoolException; + +/** + * @author Tobias Nyholm + */ +class NoPoolAvailableException extends CachePoolException +{ +} diff --git a/vendor/cache/cache/src/Adapter/Chain/Exception/PoolFailedException.php b/vendor/cache/cache/src/Adapter/Chain/Exception/PoolFailedException.php new file mode 100644 index 0000000000..50f38637eb --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/Exception/PoolFailedException.php @@ -0,0 +1,23 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Chain\Exception; + +use Cache\Adapter\Common\Exception\CachePoolException; + +/** + * When a cache pool fails with its operation. + * + * @author Tobias Nyholm + */ +class PoolFailedException extends CachePoolException +{ +} diff --git a/vendor/cache/cache/src/Adapter/Chain/LICENSE b/vendor/cache/cache/src/Adapter/Chain/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Chain/README.md b/vendor/cache/cache/src/Adapter/Chain/README.md new file mode 100644 index 0000000000..d32c73029a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/README.md @@ -0,0 +1,31 @@ +# PSR-6 Cache pool chain +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/chain-adapter/v/stable)](https://packagist.org/packages/cache/chain-adapter) +[![codecov.io](https://codecov.io/github/php-cache/chain-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/chain-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/chain-adapter/downloads)](https://packagist.org/packages/cache/chain-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/chain-adapter/d/monthly.png)](https://packagist.org/packages/cache/chain-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using a chain of other PSR-6 cache pools. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/chain-adapter +``` + +### Use + +You do not need to do any configuration to use the `CachePoolChain`. + +```php +$redisPool = new RedisCachePool($redisClient); +$apcPool = new ApcCachePool(); +$pool = new CachePoolChain([$apcPool, $redisPool]); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Chain/composer.json b/vendor/cache/cache/src/Adapter/Chain/composer.json new file mode 100644 index 0000000000..3b3efc002a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Chain/composer.json @@ -0,0 +1,56 @@ +{ + "name": "cache/chain-adapter", + "description": "A PSR-6 cache implementation using chain. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "chain", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/log": "^1.0", + "cache/adapter-common": "^1.0", + "cache/tag-interop": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/filesystem-adapter": "^1.0", + "cache/array-adapter": "^1.0", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Chain\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Common/AbstractCachePool.php b/vendor/cache/cache/src/Adapter/Common/AbstractCachePool.php new file mode 100644 index 0000000000..4760b74462 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/AbstractCachePool.php @@ -0,0 +1,558 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\Adapter\Common\Exception\CacheException; +use Cache\Adapter\Common\Exception\CachePoolException; +use Cache\Adapter\Common\Exception\InvalidArgumentException; +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +abstract class AbstractCachePool implements PhpCachePool, LoggerAwareInterface, CacheInterface +{ + const SEPARATOR_TAG = '!'; + + /** + * @type LoggerInterface + */ + private $logger; + + /** + * @type PhpCacheItem[] deferred + */ + protected $deferred = []; + + /** + * @param PhpCacheItem $item + * @param int|null $ttl seconds from now + * + * @return bool true if saved + */ + abstract protected function storeItemInCache(PhpCacheItem $item, $ttl); + + /** + * Fetch an object from the cache implementation. + * + * If it is a cache miss, it MUST return [false, null, [], null] + * + * @param string $key + * + * @return array with [isHit, value, tags[], expirationTimestamp] + */ + abstract protected function fetchObjectFromCache($key); + + /** + * Clear all objects from cache. + * + * @return bool false if error + */ + abstract protected function clearAllObjectsFromCache(); + + /** + * Remove one object from cache. + * + * @param string $key + * + * @return bool + */ + abstract protected function clearOneObjectFromCache($key); + + /** + * Get an array with all the values in the list named $name. + * + * @param string $name + * + * @return array + */ + abstract protected function getList($name); + + /** + * Remove the list. + * + * @param string $name + * + * @return bool + */ + abstract protected function removeList($name); + + /** + * Add a item key on a list named $name. + * + * @param string $name + * @param string $key + */ + abstract protected function appendListItem($name, $key); + + /** + * Remove an item from the list. + * + * @param string $name + * @param string $key + */ + abstract protected function removeListItem($name, $key); + + /** + * Make sure to commit before we destruct. + */ + public function __destruct() + { + $this->commit(); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $this->validateKey($key); + if (isset($this->deferred[$key])) { + /** @type CacheItem $item */ + $item = clone $this->deferred[$key]; + $item->moveTagsToPrevious(); + + return $item; + } + + $func = function () use ($key) { + try { + return $this->fetchObjectFromCache($key); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + }; + + return new CacheItem($key, $func); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $items = []; + foreach ($keys as $key) { + $items[$key] = $this->getItem($key); + } + + return $items; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + try { + return $this->getItem($key)->isHit(); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // Clear the deferred items + $this->deferred = []; + + try { + return $this->clearAllObjectsFromCache(); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + try { + return $this->deleteItems([$key]); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $deleted = true; + foreach ($keys as $key) { + $this->validateKey($key); + + // Delete form deferred + unset($this->deferred[$key]); + + // We have to commit here to be able to remove deferred hierarchy items + $this->commit(); + $this->preRemoveItem($key); + + if (!$this->clearOneObjectFromCache($key)) { + $deleted = false; + } + } + + return $deleted; + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof PhpCacheItem) { + $e = new InvalidArgumentException('Cache items are not transferable between pools. Item MUST implement PhpCacheItem.'); + $this->handleException($e, __FUNCTION__); + } + + $this->removeTagEntries($item); + $this->saveTags($item); + $timeToLive = null; + if (null !== $timestamp = $item->getExpirationTimestamp()) { + $timeToLive = $timestamp - time(); + + if ($timeToLive < 0) { + return $this->deleteItem($item->getKey()); + } + } + + try { + return $this->storeItemInCache($item, $timeToLive); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + $saved = true; + foreach ($this->deferred as $item) { + if (!$this->save($item)) { + $saved = false; + } + } + $this->deferred = []; + + return $saved; + } + + /** + * @param string $key + * + * @throws InvalidArgumentException + */ + protected function validateKey($key) + { + if (!is_string($key)) { + $e = new InvalidArgumentException(sprintf( + 'Cache key must be string, "%s" given', gettype($key) + )); + $this->handleException($e, __FUNCTION__); + } + if (!isset($key[0])) { + $e = new InvalidArgumentException('Cache key cannot be an empty string'); + $this->handleException($e, __FUNCTION__); + } + if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { + $e = new InvalidArgumentException(sprintf( + 'Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:', + $key + )); + $this->handleException($e, __FUNCTION__); + } + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Logs with an arbitrary level if the logger exists. + * + * @param mixed $level + * @param string $message + * @param array $context + */ + protected function log($level, $message, array $context = []) + { + if ($this->logger !== null) { + $this->logger->log($level, $message, $context); + } + } + + /** + * Log exception and rethrow it. + * + * @param \Exception $e + * @param string $function + * + * @throws CachePoolException + */ + private function handleException(\Exception $e, $function) + { + $level = 'alert'; + if ($e instanceof InvalidArgumentException) { + $level = 'warning'; + } + + $this->log($level, $e->getMessage(), ['exception' => $e]); + if (!$e instanceof CacheException) { + $e = new CachePoolException(sprintf('Exception thrown when executing "%s". ', $function), 0, $e); + } + + throw $e; + } + + /** + * @param array $tags + * + * @return bool + */ + public function invalidateTags(array $tags) + { + $itemIds = []; + foreach ($tags as $tag) { + $itemIds = array_merge($itemIds, $this->getList($this->getTagKey($tag))); + } + + // Remove all items with the tag + $success = $this->deleteItems($itemIds); + + if ($success) { + // Remove the tag list + foreach ($tags as $tag) { + $this->removeList($this->getTagKey($tag)); + $l = $this->getList($this->getTagKey($tag)); + } + } + + return $success; + } + + public function invalidateTag($tag) + { + return $this->invalidateTags([$tag]); + } + + /** + * @param PhpCacheItem $item + */ + protected function saveTags(PhpCacheItem $item) + { + $tags = $item->getTags(); + foreach ($tags as $tag) { + $this->appendListItem($this->getTagKey($tag), $item->getKey()); + } + } + + /** + * Removes the key form all tag lists. When an item with tags is removed + * we MUST remove the tags. If we fail to remove the tags a new item with + * the same key will automatically get the previous tags. + * + * @param string $key + * + * @return $this + */ + protected function preRemoveItem($key) + { + $item = $this->getItem($key); + $this->removeTagEntries($item); + + return $this; + } + + /** + * @param PhpCacheItem $item + */ + private function removeTagEntries(PhpCacheItem $item) + { + $tags = $item->getPreviousTags(); + foreach ($tags as $tag) { + $this->removeListItem($this->getTagKey($tag), $item->getKey()); + } + } + + /** + * @param string $tag + * + * @return string + */ + protected function getTagKey($tag) + { + return 'tag'.self::SEPARATOR_TAG.$tag; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $item = $this->getItem($key); + if (!$item->isHit()) { + return $default; + } + + return $item->get(); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $item = $this->getItem($key); + $item->set($value); + $item->expiresAfter($ttl); + + return $this->save($item); + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + return $this->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + if (!is_array($keys)) { + if (!$keys instanceof \Traversable) { + throw new InvalidArgumentException('$keys is neither an array nor Traversable'); + } + + // Since we need to throw an exception if *any* key is invalid, it doesn't + // make sense to wrap iterators or something like that. + $keys = iterator_to_array($keys, false); + } + + $items = $this->getItems($keys); + + return $this->generateValues($default, $items); + } + + /** + * @param $default + * @param $items + * + * @return \Generator + */ + private function generateValues($default, $items) + { + foreach ($items as $key => $item) { + /** @type $item CacheItemInterface */ + if (!$item->isHit()) { + yield $key => $default; + } else { + yield $key => $item->get(); + } + } + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + if (!is_array($values)) { + if (!$values instanceof \Traversable) { + throw new InvalidArgumentException('$values is neither an array nor Traversable'); + } + } + + $keys = []; + $arrayValues = []; + foreach ($values as $key => $value) { + if (is_int($key)) { + $key = (string) $key; + } + $this->validateKey($key); + $keys[] = $key; + $arrayValues[$key] = $value; + } + + $items = $this->getItems($keys); + $itemSuccess = true; + foreach ($items as $key => $item) { + $item->set($arrayValues[$key]); + + try { + $item->expiresAfter($ttl); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $itemSuccess = $itemSuccess && $this->saveDeferred($item); + } + + return $itemSuccess && $this->commit(); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + if (!is_array($keys)) { + if (!$keys instanceof \Traversable) { + throw new InvalidArgumentException('$keys is neither an array nor Traversable'); + } + + // Since we need to throw an exception if *any* key is invalid, it doesn't + // make sense to wrap iterators or something like that. + $keys = iterator_to_array($keys, false); + } + + return $this->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return $this->hasItem($key); + } +} diff --git a/vendor/cache/cache/src/Adapter/Common/CacheItem.php b/vendor/cache/cache/src/Adapter/Common/CacheItem.php new file mode 100644 index 0000000000..47c0081c12 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/CacheItem.php @@ -0,0 +1,269 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\Adapter\Common\Exception\InvalidArgumentException; +use Cache\TagInterop\TaggableCacheItemInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +class CacheItem implements PhpCacheItem +{ + /** + * @type array + */ + private $prevTags = []; + + /** + * @type array + */ + private $tags = []; + + /** + * @type \Closure + */ + private $callable; + + /** + * @type string + */ + private $key; + + /** + * @type mixed + */ + private $value; + + /** + * The expiration timestamp is the source of truth. This is the UTC timestamp + * when the cache item expire. A value of zero means it never expires. A nullvalue + * means that no expiration is set. + * + * @type int|null + */ + private $expirationTimestamp = null; + + /** + * @type bool + */ + private $hasValue = false; + + /** + * @param string $key + * @param \Closure|bool $callable or boolean hasValue + */ + public function __construct($key, $callable = null, $value = null) + { + $this->key = $key; + + if ($callable === true) { + $this->hasValue = true; + $this->value = $value; + } elseif ($callable !== false) { + // This must be a callable or null + $this->callable = $callable; + } + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->key; + } + + /** + * {@inheritdoc} + */ + public function set($value) + { + $this->value = $value; + $this->hasValue = true; + $this->callable = null; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if (!$this->isHit()) { + return; + } + + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function isHit() + { + $this->initialize(); + + if (!$this->hasValue) { + return false; + } + + if ($this->expirationTimestamp !== null) { + return $this->expirationTimestamp > time(); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getExpirationTimestamp() + { + return $this->expirationTimestamp; + } + + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + if ($expiration instanceof \DateTimeInterface) { + $this->expirationTimestamp = $expiration->getTimestamp(); + } elseif (is_int($expiration) || null === $expiration) { + $this->expirationTimestamp = $expiration; + } else { + throw new InvalidArgumentException('Cache item ttl/expiresAt must be of type integer or \DateTimeInterface.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + if ($time === null) { + $this->expirationTimestamp = null; + } elseif ($time instanceof \DateInterval) { + $date = new \DateTime(); + $date->add($time); + $this->expirationTimestamp = $date->getTimestamp(); + } elseif (is_int($time)) { + $this->expirationTimestamp = time() + $time; + } else { + throw new InvalidArgumentException('Cache item ttl/expiresAfter must be of type integer or \DateInterval.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPreviousTags() + { + $this->initialize(); + + return $this->prevTags; + } + + /** + * {@inheritdoc} + */ + public function getTags() + { + return $this->tags; + } + + /** + * {@inheritdoc} + */ + public function setTags(array $tags) + { + $this->tags = []; + $this->tag($tags); + + return $this; + } + + /** + * Adds a tag to a cache item. + * + * @param string|string[] $tags A tag or array of tags + * + * @throws InvalidArgumentException When $tag is not valid. + * + * @return TaggableCacheItemInterface + */ + private function tag($tags) + { + $this->initialize(); + + if (!is_array($tags)) { + $tags = [$tags]; + } + foreach ($tags as $tag) { + if (!is_string($tag)) { + throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag))); + } + if (isset($this->tags[$tag])) { + continue; + } + if (!isset($tag[0])) { + throw new InvalidArgumentException('Cache tag length must be greater than zero'); + } + if (isset($tag[strcspn($tag, '{}()/\@:')])) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag)); + } + $this->tags[$tag] = $tag; + } + + return $this; + } + + /** + * If callable is not null, execute it an populate this object with values. + */ + private function initialize() + { + if ($this->callable !== null) { + // $f will be $adapter->fetchObjectFromCache(); + $f = $this->callable; + $result = $f(); + $this->hasValue = $result[0]; + $this->value = $result[1]; + $this->prevTags = isset($result[2]) ? $result[2] : []; + $this->expirationTimestamp = null; + + if (isset($result[3]) && is_int($result[3])) { + $this->expirationTimestamp = $result[3]; + } + + $this->callable = null; + } + } + + /** + * @internal This function should never be used and considered private. + * + * Move tags from $tags to $prevTags + */ + public function moveTagsToPrevious() + { + $this->prevTags = $this->tags; + $this->tags = []; + } +} diff --git a/vendor/cache/cache/src/Adapter/Common/Changelog.md b/vendor/cache/cache/src/Adapter/Common/Changelog.md new file mode 100644 index 0000000000..c896e891dd --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/Changelog.md @@ -0,0 +1,47 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 1.0.0 + +No changes since 0.4.0. + +## 0.4.0 + +### Added + +* `AbstractCachePool` has 4 new abstract methods: `getList`, `removeList`, `appendListItem` and `removeListItem`. +* `AbstractCachePool::invalidateTags` and `AbstractCachePool::invalidateTags` +* Added interfaces for our items and pools `PhpCachePool` and `PhpCacheItem` +* Trait to help adapters to support tags. `TagSupportWithArray`. + +### Changed + +* First parameter to `AbstractCachePool::storeItemInCache` must be a `PhpCacheItem`. +* Return value from `AbstractCachePool::fetchObjectFromCache` must be a an array with 4 values. Added expiration timestamp. +* `HasExpirationDateInterface` is replaced by `HasExpirationTimestampInterface` +* We do not work with `\DateTime` internally anymore. We work with timestamps. + +## 0.3.3 + +### Fixed + +* Bugfix when you fetch data from the cache storage that was saved as "non-tagging item" but fetch as a tagging item. + +## 0.3.2 + +### Added + +* Cache pools do implement `LoggerAwareInterface` + +## 0.3.0 + +### Changed + +* The `AbstractCachePool` does not longer implement `TaggablePoolInterface`. However, the `CacheItem` does still implement `TaggableItemInterface`. +* `CacheItem::getKeyFromTaggedKey` has been removed +* The `CacheItem`'s second parameter is a callable that must return an array with 3 elements; [`hasValue`, `value`, `tags`]. + +## 0.2.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Common/Exception/CacheException.php b/vendor/cache/cache/src/Adapter/Common/Exception/CacheException.php new file mode 100644 index 0000000000..54fbb11593 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/Exception/CacheException.php @@ -0,0 +1,23 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common\Exception; + +use Psr\Cache\CacheException as CacheExceptionInterface; + +/** + * A base exception. All exceptions in this organization will extend this exception. + * + * @author Tobias Nyholm + */ +abstract class CacheException extends \RuntimeException implements CacheExceptionInterface +{ +} diff --git a/vendor/cache/cache/src/Adapter/Common/Exception/CachePoolException.php b/vendor/cache/cache/src/Adapter/Common/Exception/CachePoolException.php new file mode 100644 index 0000000000..c0b7e59964 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/Exception/CachePoolException.php @@ -0,0 +1,21 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common\Exception; + +/** + * If an exception is caused by a pool or by the cache storage. + * + * @author Tobias Nyholm + */ +class CachePoolException extends CacheException +{ +} diff --git a/vendor/cache/cache/src/Adapter/Common/Exception/InvalidArgumentException.php b/vendor/cache/cache/src/Adapter/Common/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..e3cc8f443a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common\Exception; + +use Psr\Cache\InvalidArgumentException as CacheInvalidArgumentException; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentException; + +class InvalidArgumentException extends CacheException implements CacheInvalidArgumentException, SimpleCacheInvalidArgumentException +{ +} diff --git a/vendor/cache/cache/src/Adapter/Common/HasExpirationTimestampInterface.php b/vendor/cache/cache/src/Adapter/Common/HasExpirationTimestampInterface.php new file mode 100644 index 0000000000..22f0adf219 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/HasExpirationTimestampInterface.php @@ -0,0 +1,26 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +interface HasExpirationTimestampInterface +{ + /** + * The timestamp when the object expires. + * + * @return int|null + */ + public function getExpirationTimestamp(); +} diff --git a/vendor/cache/cache/src/Adapter/Common/LICENSE b/vendor/cache/cache/src/Adapter/Common/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Common/PhpCacheItem.php b/vendor/cache/cache/src/Adapter/Common/PhpCacheItem.php new file mode 100644 index 0000000000..8d6ed5eaea --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/PhpCacheItem.php @@ -0,0 +1,32 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\TagInterop\TaggableCacheItemInterface; + +/** + * @author Tobias Nyholm + */ +interface PhpCacheItem extends HasExpirationTimestampInterface, TaggableCacheItemInterface +{ + /** + * Get the current tags. These are not the same tags as getPrevious tags. This + * is the tags that has been added to the item after the item was fetched from + * the cache storage. + * + * WARNING: This is generally not the function you want to use. Please see + * `getPreviousTags`. + * + * @return array + */ + public function getTags(); +} diff --git a/vendor/cache/cache/src/Adapter/Common/PhpCachePool.php b/vendor/cache/cache/src/Adapter/Common/PhpCachePool.php new file mode 100644 index 0000000000..5ccfb6718e --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/PhpCachePool.php @@ -0,0 +1,34 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\TagInterop\TaggableCacheItemPoolInterface; + +/** + * @author Tobias Nyholm + */ +interface PhpCachePool extends TaggableCacheItemPoolInterface +{ + /** + * {@inheritdoc} + * + * @return PhpCacheItem + */ + public function getItem($key); + + /** + * {@inheritdoc} + * + * @return array|\Traversable|PhpCacheItem[] + */ + public function getItems(array $keys = []); +} diff --git a/vendor/cache/cache/src/Adapter/Common/README.md b/vendor/cache/cache/src/Adapter/Common/README.md new file mode 100644 index 0000000000..4a806770aa --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/README.md @@ -0,0 +1,16 @@ +# Common PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/adapter-common/v/stable)](https://packagist.org/packages/cache/adapter-common) +[![codecov.io](https://codecov.io/github/php-cache/adapter-common/coverage.svg?branch=master)](https://codecov.io/github/php-cache/adapter-common?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/adapter-common/downloads)](https://packagist.org/packages/cache/adapter-common) +[![Monthly Downloads](https://poser.pugx.org/cache/adapter-common/d/monthly.png)](https://packagist.org/packages/cache/adapter-common) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository contains shared classes and interfaces used by the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Common/TagSupportWithArray.php b/vendor/cache/cache/src/Adapter/Common/TagSupportWithArray.php new file mode 100644 index 0000000000..81859d2b6e --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/TagSupportWithArray.php @@ -0,0 +1,88 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +/** + * This trait could be used by adapters that do not have a native support for lists. + * + * @author Tobias Nyholm + */ +trait TagSupportWithArray +{ + /** + * Get a value from the storage. + * + * @param string $name + * + * @return mixed + */ + abstract public function getDirectValue($name); + + /** + * Set a value to the storage. + * + * @param string $name + * @param mixed $value + */ + abstract public function setDirectValue($name, $value); + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $value) + { + $data = $this->getDirectValue($name); + if (!is_array($data)) { + $data = []; + } + $data[] = $value; + $this->setDirectValue($name, $data); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + $data = $this->getDirectValue($name); + if (!is_array($data)) { + $data = []; + } + + return $data; + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + $this->setDirectValue($name, []); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $data = $this->getList($name); + foreach ($data as $i => $value) { + if ($key === $value) { + unset($data[$i]); + } + } + + return $this->setDirectValue($name, $data); + } +} diff --git a/vendor/cache/cache/src/Adapter/Common/composer.json b/vendor/cache/cache/src/Adapter/Common/composer.json new file mode 100644 index 0000000000..0e56545746 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Common/composer.json @@ -0,0 +1,55 @@ +{ + "name": "cache/adapter-common", + "description": "Common classes for PSR-6 adapters", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "psr/log": "^1.0", + "cache/tag-interop": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Common\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "Cache\\Adapter\\Common\\Tests\\": "Tests/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Doctrine/Changelog.md b/vendor/cache/cache/src/Adapter/Doctrine/Changelog.md new file mode 100644 index 0000000000..8ce6119593 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Doctrine/Changelog.md @@ -0,0 +1,36 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.6.0 + +## 0.6.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.5.1 + +### Changed + +* The `DoctrineCachePool::$cache` is now protected instead of private + +## 0.5.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Doctrine/DoctrineCachePool.php b/vendor/cache/cache/src/Adapter/Doctrine/DoctrineCachePool.php new file mode 100644 index 0000000000..3fd333d2e7 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Doctrine/DoctrineCachePool.php @@ -0,0 +1,137 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Doctrine; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Cache\FlushableCache; + +/** + * This is a bridge between PSR-6 and aDoctrine cache. + * + * @author Aaron Scherer + * @author Tobias Nyholm + */ +class DoctrineCachePool extends AbstractCachePool +{ + /** + * @type Cache + */ + protected $cache; + + /** + * @param Cache $cache + */ + public function __construct(Cache $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if (false === $data = $this->cache->fetch($key)) { + return [false, null, [], null]; + } + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + if ($this->cache instanceof FlushableCache) { + return $this->cache->flushAll(); + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + return $this->cache->delete($key); + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + if ($ttl === null) { + $ttl = 0; + } + + $data = serialize([true, $item->get(), $item->getTags(), $item->getExpirationTimestamp()]); + + return $this->cache->save($item->getKey(), $data, $ttl); + } + + /** + * @return Cache + */ + public function getCache() + { + return $this->cache; + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + if (false === $list = $this->cache->fetch($name)) { + return []; + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + return $this->cache->delete($name); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $key) + { + $list = $this->getList($name); + $list[] = $key; + $this->cache->save($name, $list); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $list = $this->getList($name); + foreach ($list as $i => $item) { + if ($item === $key) { + unset($list[$i]); + } + } + $this->cache->save($name, $list); + } +} diff --git a/vendor/cache/cache/src/Adapter/Doctrine/LICENSE b/vendor/cache/cache/src/Adapter/Doctrine/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Doctrine/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Doctrine/README.md b/vendor/cache/cache/src/Adapter/Doctrine/README.md new file mode 100644 index 0000000000..1ffe78328c --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Doctrine/README.md @@ -0,0 +1,43 @@ +# Doctrine PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/doctrine-adapter/v/stable)](https://packagist.org/packages/cache/doctrine-adapter) +[![codecov.io](https://codecov.io/github/php-cache/doctrine-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/doctrine-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/doctrine-adapter/downloads)](https://packagist.org/packages/cache/doctrine-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/doctrine-adapter/d/monthly.png)](https://packagist.org/packages/cache/doctrine-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Doctrine cache. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +This is a PSR-6 to Doctrine bridge. If you are interested in a Doctrine to PSR-6 bridge you should have a look at +[PSR-6 Doctrine Bridge](https://github.com/php-cache/doctrine-bridge). + +### Install + +```bash +composer require cache/doctrine-adapter +``` + +## Use + +```php +use Doctrine\Common\Cache\MemcachedCache; +use Cache\Adapter\Doctrine\DoctrineCachePool; + + +$memcached = new \Memcached(); +$memcached->addServer('localhost', 11211); + +// Create a instance of Doctrine's MemcachedCache +$doctrineCache = new MemcachedCache(); +$doctrineCache->setMemcached($memcached); + +// Wrap Doctrine's cache with the PSR-6 adapter +$pool = new DoctrineCachePool($doctrineCache); +``` + + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Doctrine/composer.json b/vendor/cache/cache/src/Adapter/Doctrine/composer.json new file mode 100644 index 0000000000..5f19b27524 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Doctrine/composer.json @@ -0,0 +1,61 @@ +{ + "name": "cache/doctrine-adapter", + "description": "A PSR-6 cache implementation using Doctrine. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "doctrine", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "doctrine/cache": "^1.6" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "mockery/mockery": "^0.9.9", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "suggest": { + "ext-apc": "Allows for caching with Apc", + "ext-memcache": "Allows for caching with Memcache", + "ext-memcached": "Allows for caching with Memcached", + "ext-redis": "Allows for caching with Redis" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Filesystem/Changelog.md b/vendor/cache/cache/src/Adapter/Filesystem/Changelog.md new file mode 100644 index 0000000000..6c381741c7 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Filesystem/Changelog.md @@ -0,0 +1,48 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.4.0 + +## 0.4.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.3.3 + +### Fixed + +Race condition in `fetchObjectFromCache`. + +## 0.3.2 + +### Changed + +Using `Filesystem::update` instead of `Filesystem::delete` and `Filesystem::write`. + +## 0.3.1 + +### Added + +* Add ability to change cache path in FilesystemCachePool + +## 0.3.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Filesystem/FilesystemCachePool.php b/vendor/cache/cache/src/Adapter/Filesystem/FilesystemCachePool.php new file mode 100644 index 0000000000..0e6d8af7da --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Filesystem/FilesystemCachePool.php @@ -0,0 +1,213 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Filesystem; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\Exception\InvalidArgumentException; +use Cache\Adapter\Common\PhpCacheItem; +use League\Flysystem\FileExistsException; +use League\Flysystem\FileNotFoundException; +use League\Flysystem\Filesystem; + +/** + * @author Tobias Nyholm + */ +class FilesystemCachePool extends AbstractCachePool +{ + /** + * @type Filesystem + */ + private $filesystem; + + /** + * The folder should not begin nor end with a slash. Example: path/to/cache. + * + * @type string + */ + private $folder; + + /** + * @param Filesystem $filesystem + * @param string $folder + */ + public function __construct(Filesystem $filesystem, $folder = 'cache') + { + $this->folder = $folder; + + $this->filesystem = $filesystem; + $this->filesystem->createDir($this->folder); + } + + /** + * @param string $folder + */ + public function setFolder($folder) + { + $this->folder = $folder; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + $empty = [false, null, [], null]; + $file = $this->getFilePath($key); + + try { + $data = @unserialize($this->filesystem->read($file)); + if ($data === false) { + return $empty; + } + } catch (FileNotFoundException $e) { + return $empty; + } + + // Determine expirationTimestamp from data, remove items if expired + $expirationTimestamp = $data[2] ?: null; + if ($expirationTimestamp !== null && time() > $expirationTimestamp) { + foreach ($data[1] as $tag) { + $this->removeListItem($this->getTagKey($tag), $key); + } + $this->forceClear($key); + + return $empty; + } + + return [true, $data[0], $data[1], $expirationTimestamp]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + $this->filesystem->deleteDir($this->folder); + $this->filesystem->createDir($this->folder); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + return $this->forceClear($key); + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $data = serialize( + [ + $item->get(), + $item->getTags(), + $item->getExpirationTimestamp(), + ] + ); + + $file = $this->getFilePath($item->getKey()); + if ($this->filesystem->has($file)) { + // Update file if it exists + return $this->filesystem->update($file, $data); + } + + try { + return $this->filesystem->write($file, $data); + } catch (FileExistsException $e) { + // To handle issues when/if race conditions occurs, we try to update here. + return $this->filesystem->update($file, $data); + } + } + + /** + * @param string $key + * + * @throws InvalidArgumentException + * + * @return string + */ + private function getFilePath($key) + { + if (!preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key)) { + throw new InvalidArgumentException(sprintf('Invalid key "%s". Valid filenames must match [a-zA-Z0-9_\.! ].', $key)); + } + + return sprintf('%s/%s', $this->folder, $key); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + $file = $this->getFilePath($name); + + if (!$this->filesystem->has($file)) { + $this->filesystem->write($file, serialize([])); + } + + return unserialize($this->filesystem->read($file)); + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + $file = $this->getFilePath($name); + $this->filesystem->delete($file); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $key) + { + $list = $this->getList($name); + $list[] = $key; + + return $this->filesystem->update($this->getFilePath($name), serialize($list)); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $list = $this->getList($name); + foreach ($list as $i => $item) { + if ($item === $key) { + unset($list[$i]); + } + } + + return $this->filesystem->update($this->getFilePath($name), serialize($list)); + } + + /** + * @param $key + * + * @return bool + */ + private function forceClear($key) + { + try { + return $this->filesystem->delete($this->getFilePath($key)); + } catch (FileNotFoundException $e) { + return true; + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Filesystem/LICENSE b/vendor/cache/cache/src/Adapter/Filesystem/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Filesystem/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Filesystem/README.md b/vendor/cache/cache/src/Adapter/Filesystem/README.md new file mode 100644 index 0000000000..90b4e45075 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Filesystem/README.md @@ -0,0 +1,45 @@ +# Filesystem PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/filesystem-adapter/v/stable)](https://packagist.org/packages/cache/filesystem-adapter) +[![codecov.io](https://codecov.io/github/php-cache/filesystem-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/filesystem-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/filesystem-adapter/downloads)](https://packagist.org/packages/cache/filesystem-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/filesystem-adapter/d/monthly.png)](https://packagist.org/packages/cache/filesystem-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Filesystem. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +This implementation is using the excellent [Flysystem](http://flysystem.thephpleague.com/). + +### Install + +```bash +composer require cache/filesystem-adapter +``` + +### Use + +To create an instance of `FilesystemCachePool` you need to configure a `Filesystem` and its adapter. + +```php +use League\Flysystem\Adapter\Local; +use League\Flysystem\Filesystem; +use Cache\Adapter\Filesystem\FilesystemCachePool; + +$filesystemAdapter = new Local(__DIR__.'/'); +$filesystem = new Filesystem($filesystemAdapter); + +$pool = new FilesystemCachePool($filesystem); +``` + +You can change the folder the cache pool will write to through the `setFolder` setter: + +```php +$pool = new FilesystemCachePool($filesystem); +$pool->setFolder('path/to/cache'); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Filesystem/composer.json b/vendor/cache/cache/src/Adapter/Filesystem/composer.json new file mode 100644 index 0000000000..cbaa8806d4 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Filesystem/composer.json @@ -0,0 +1,54 @@ +{ + "name": "cache/filesystem-adapter", + "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "filesystem", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "league/flysystem": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Illuminate/Changelog.md b/vendor/cache/cache/src/Adapter/Illuminate/Changelog.md new file mode 100644 index 0000000000..0c39f697f3 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Illuminate/Changelog.md @@ -0,0 +1,7 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 0.1.0 + +First version diff --git a/vendor/cache/cache/src/Adapter/Illuminate/IlluminateCachePool.php b/vendor/cache/cache/src/Adapter/Illuminate/IlluminateCachePool.php new file mode 100644 index 0000000000..56efe651f1 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Illuminate/IlluminateCachePool.php @@ -0,0 +1,128 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Illuminate; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Illuminate\Contracts\Cache\Store; + +/** + * This is a bridge between PSR-6 and an Illuminate cache store. + * + * @author Florian Voutzinos + */ +class IlluminateCachePool extends AbstractCachePool +{ + /** + * @type Store + */ + protected $store; + + /** + * @param Store $store + */ + public function __construct(Store $store) + { + $this->store = $store; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $ttl = null === $ttl ? 0 : $ttl / 60; + + $data = serialize([true, $item->get(), $item->getTags(), $item->getExpirationTimestamp()]); + + $this->store->put($item->getKey(), $data, $ttl); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if (null === $data = $this->store->get($key)) { + return [false, null, [], null]; + } + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return $this->store->flush(); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + return $this->store->forget($key); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + $list = $this->store->get($name); + + if (!is_array($list)) { + return []; + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + return $this->store->forget($name); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $key) + { + $list = $this->getList($name); + $list[] = $key; + + $this->store->forever($name, $list); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $list = $this->getList($name); + + foreach ($list as $i => $item) { + if ($item === $key) { + unset($list[$i]); + } + } + + $this->store->forever($name, $list); + } +} diff --git a/vendor/cache/cache/src/Adapter/Illuminate/LICENSE b/vendor/cache/cache/src/Adapter/Illuminate/LICENSE new file mode 100644 index 0000000000..0a65e395f1 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Illuminate/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/cache/cache/src/Adapter/Illuminate/README.md b/vendor/cache/cache/src/Adapter/Illuminate/README.md new file mode 100644 index 0000000000..ed3fbd3f4f --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Illuminate/README.md @@ -0,0 +1,37 @@ +# Illuminate PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/illuminate-adapter/v/stable)](https://packagist.org/packages/cache/illuminate-adapter) +[![codecov.io](https://codecov.io/github/php-cache/illuminate-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/illuminate-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/illuminate-adapter/downloads)](https://packagist.org/packages/cache/illuminate-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/illuminate-adapter/d/monthly.png)](https://packagist.org/packages/cache/illuminate-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Illuminate cache. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +This is a PSR-6 to Illuminate bridge. + +### Install + +```bash +composer require cache/illuminate-adapter +``` + +## Use + +```php +use Illuminate\Cache\ArrayStore; +use Cache\Adapter\Illuminate\IlluminateCachePool; + +// Create an instance of an Illuminate's Store +$store = new ArrayStore(); + +// Wrap the Illuminate's store with the PSR-6 adapter +$pool = new IlluminateCachePool($store); +``` + + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Illuminate/composer.json b/vendor/cache/cache/src/Adapter/Illuminate/composer.json new file mode 100644 index 0000000000..4552cc8250 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Illuminate/composer.json @@ -0,0 +1,61 @@ +{ + "name": "cache/illuminate-adapter", + "description": "A PSR-6 cache implementation using Illuminate. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "illuminate", + "laravel", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "https://github.com/florianv" + }, + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "illuminate/cache": "^5.4" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "mockery/mockery": "^0.9", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Illuminate\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Memcache/Changelog.md b/vendor/cache/cache/src/Adapter/Memcache/Changelog.md new file mode 100644 index 0000000000..079e669a6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcache/Changelog.md @@ -0,0 +1,40 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.3.0 + +## 0.3.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.2.2 + +### Added + +Support for PHP7 + +### Changed + +* The `MemcachedCachePool::$cache` is now protected instead of private + +## 0.2.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Memcache/LICENSE b/vendor/cache/cache/src/Adapter/Memcache/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcache/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Memcache/MemcacheCachePool.php b/vendor/cache/cache/src/Adapter/Memcache/MemcacheCachePool.php new file mode 100644 index 0000000000..8efedd9dee --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcache/MemcacheCachePool.php @@ -0,0 +1,91 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Memcache; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Adapter\Common\TagSupportWithArray; +use Memcache; + +class MemcacheCachePool extends AbstractCachePool +{ + use TagSupportWithArray; + + /** + * @type Memcache + */ + protected $cache; + + /** + * @param Memcache $cache + */ + public function __construct(Memcache $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if (false === $result = unserialize($this->cache->get($key))) { + return [false, null, [], null]; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return $this->cache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + $this->cache->delete($key); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $data = serialize([true, $item->get(), $item->getTags(), $item->getExpirationTimestamp()]); + + return $this->cache->set($item->getKey(), $data, 0, $ttl ?: 0); + } + + /** + * {@inheritdoc} + */ + public function getDirectValue($name) + { + return $this->cache->get($name); + } + + /** + * {@inheritdoc} + */ + public function setDirectValue($name, $value) + { + $this->cache->set($name, $value); + } +} diff --git a/vendor/cache/cache/src/Adapter/Memcache/README.md b/vendor/cache/cache/src/Adapter/Memcache/README.md new file mode 100644 index 0000000000..5f1a5de230 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcache/README.md @@ -0,0 +1,31 @@ +# Memcache PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/memcache-adapter/v/stable)](https://packagist.org/packages/cache/memcache-adapter) +[![codecov.io](https://codecov.io/github/php-cache/memcache-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/memcache-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/memcache-adapter/downloads)](https://packagist.org/packages/cache/memcache-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/memcache-adapter/d/monthly.png)](https://packagist.org/packages/cache/memcache-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Memcache. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/memcache-adapter +``` + +### Use + +To create an instance of `MemcacheCachePool` you need to configure a `\Memcache` client. + +```php +$client = new Memcache(); +$client->connect('localhost', 11211); +$pool = new MemcacheCachePool($client); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Memcache/composer.json b/vendor/cache/cache/src/Adapter/Memcache/composer.json new file mode 100644 index 0000000000..32eba4d68a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcache/composer.json @@ -0,0 +1,61 @@ +{ + "name": "cache/memcache-adapter", + "description": "A PSR-6 cache implementation using memcache. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "memcache", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "suggest": { + "ext-memcache": "The extension required to use this pool." + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Memcache\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "Cache\\Adapter\\Memcache\\Tests\\": "Tests/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Memcached/Changelog.md b/vendor/cache/cache/src/Adapter/Memcached/Changelog.md new file mode 100644 index 0000000000..87b044a61a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcached/Changelog.md @@ -0,0 +1,45 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Fixed + +* Fixed `$path` variable not initialized in `clearOneObjectFromCache`. + +## 0.4.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.3.3 + +### Fixed + +* Issue when ttl is larger than 30 days. + +## 0.3.2 + +### Changed + +* The `MemcachedCachePool::$cache` is now protected instead of private +* Using `cache/hierarchical-cache:^0.3` + +## 0.3.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Memcached/LICENSE b/vendor/cache/cache/src/Adapter/Memcached/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcached/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Memcached/MemcachedCachePool.php b/vendor/cache/cache/src/Adapter/Memcached/MemcachedCachePool.php new file mode 100644 index 0000000000..ad3eca1f4a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcached/MemcachedCachePool.php @@ -0,0 +1,119 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Memcached; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Adapter\Common\TagSupportWithArray; +use Cache\Hierarchy\HierarchicalCachePoolTrait; +use Cache\Hierarchy\HierarchicalPoolInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +class MemcachedCachePool extends AbstractCachePool implements HierarchicalPoolInterface +{ + use HierarchicalCachePoolTrait; + use TagSupportWithArray; + + /** + * @type \Memcached + */ + protected $cache; + + /** + * @param \Memcached $cache + */ + public function __construct(\Memcached $cache) + { + $this->cache = $cache; + $this->cache->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if (false === $result = unserialize($this->cache->get($this->getHierarchyKey($key)))) { + return [false, null, [], null]; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return $this->cache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + $this->commit(); + $path = null; + $key = $this->getHierarchyKey($key, $path); + if ($path) { + $this->cache->increment($path, 1, 0); + } + $this->clearHierarchyKeyCache(); + + if ($this->cache->delete($key)) { + return true; + } + + // Return true if key not found + return $this->cache->getResultCode() === \Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + if ($ttl === null) { + $ttl = 0; + } elseif ($ttl < 0) { + return false; + } elseif ($ttl > 86400 * 30) { + // Any time higher than 30 days is interpreted as a unix timestamp date. + // https://github.com/memcached/memcached/wiki/Programming#expiration + $ttl = time() + $ttl; + } + + $key = $this->getHierarchyKey($item->getKey()); + + return $this->cache->set($key, serialize([true, $item->get(), $item->getTags(), $item->getExpirationTimestamp()]), $ttl); + } + + /** + * {@inheritdoc} + */ + public function getDirectValue($name) + { + return $this->cache->get($name); + } + + /** + * {@inheritdoc} + */ + public function setDirectValue($name, $value) + { + $this->cache->set($name, $value); + } +} diff --git a/vendor/cache/cache/src/Adapter/Memcached/README.md b/vendor/cache/cache/src/Adapter/Memcached/README.md new file mode 100644 index 0000000000..3f31922690 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcached/README.md @@ -0,0 +1,31 @@ +# Memcached PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/memcached-adapter/v/stable)](https://packagist.org/packages/cache/memcached-adapter) +[![codecov.io](https://codecov.io/github/php-cache/memcached-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/memcached-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/memcached-adapter/downloads)](https://packagist.org/packages/cache/memcached-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/memcached-adapter/d/monthly.png)](https://packagist.org/packages/cache/memcached-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Memcached. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/memcached-adapter +``` + +### Use + +To create an instance of `MemcachedCachePool` you need to configure a `\Memcached` client. + +```php +$client = new \Memcached(); +$client->addServer('localhost', 11211); +$pool = new MemcachedCachePool($client); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Memcached/composer.json b/vendor/cache/cache/src/Adapter/Memcached/composer.json new file mode 100644 index 0000000000..c5f35b140e --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Memcached/composer.json @@ -0,0 +1,57 @@ +{ + "name": "cache/memcached-adapter", + "description": "A PSR-6 cache implementation using Memcached. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "memcached", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "cache/hierarchical-cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "suggest": { + "ext-memcached": "The extension required to use this pool." + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Memcached\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/MongoDB/Changelog.md b/vendor/cache/cache/src/Adapter/MongoDB/Changelog.md new file mode 100644 index 0000000000..9a8cd8700f --- /dev/null +++ b/vendor/cache/cache/src/Adapter/MongoDB/Changelog.md @@ -0,0 +1,30 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.3.0 + +## 0.3.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.2.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/MongoDB/LICENSE b/vendor/cache/cache/src/Adapter/MongoDB/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/MongoDB/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/MongoDB/MongoDBCachePool.php b/vendor/cache/cache/src/Adapter/MongoDB/MongoDBCachePool.php new file mode 100644 index 0000000000..922deca035 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/MongoDB/MongoDBCachePool.php @@ -0,0 +1,142 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\MongoDB; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Adapter\Common\TagSupportWithArray; +use MongoDB\Collection; +use MongoDB\Driver\Manager; + +/** + * @author Tobias Nyholm + * @author Magnus Nordlander + */ +class MongoDBCachePool extends AbstractCachePool +{ + use TagSupportWithArray; + + /** + * @type Collection + */ + private $collection; + + /** + * @param Collection $collection + */ + public function __construct(Collection $collection) + { + $this->collection = $collection; + } + + /** + * @param Manager $manager + * @param string $database + * @param string $collection + * + * @return Collection + */ + public static function createCollection(Manager $manager, $database, $collection) + { + $collection = new Collection($manager, $database, $collection); + $collection->createIndex(['expireAt' => 1], ['expireAfterSeconds' => 0]); + + return $collection; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + $object = $this->collection->findOne(['_id' => $key]); + + if (!$object || !isset($object->data)) { + return [false, null, [], null]; + } + + if (isset($object->expiresAt)) { + if ($object->expiresAt < time()) { + return [false, null, [], null]; + } + } + + return [true, unserialize($object->data), unserialize($object->tags), $object->expirationTimestamp]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + $this->collection->deleteMany([]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + $this->collection->deleteOne(['_id' => $key]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $object = [ + '_id' => $item->getKey(), + 'data' => serialize($item->get()), + 'tags' => serialize($item->getTags()), + 'expirationTimestamp' => $item->getExpirationTimestamp(), + ]; + + if ($ttl) { + $object['expiresAt'] = time() + $ttl; + } + + $this->collection->updateOne(['_id' => $item->getKey()], ['$set' => $object], ['upsert' => true]); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getDirectValue($name) + { + $object = $this->collection->findOne(['_id' => $name]); + if (!$object || !isset($object->data)) { + return; + } + + return unserialize($object->data); + } + + /** + * {@inheritdoc} + */ + public function setDirectValue($name, $value) + { + $object = [ + '_id' => $name, + 'data' => serialize($value), + ]; + + $this->collection->updateOne(['_id' => $name], ['$set' => $object], ['upsert' => true]); + } +} diff --git a/vendor/cache/cache/src/Adapter/MongoDB/README.md b/vendor/cache/cache/src/Adapter/MongoDB/README.md new file mode 100644 index 0000000000..47b808f2e6 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/MongoDB/README.md @@ -0,0 +1,30 @@ +# MongoDB PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/mongodb-adapter/v/stable)](https://packagist.org/packages/cache/mongodb-adapter) +[![codecov.io](https://codecov.io/github/php-cache/mongodb-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/mongodb-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/mongodb-adapter/downloads)](https://packagist.org/packages/cache/mongodb-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/mongodb-adapter/d/monthly.png)](https://packagist.org/packages/cache/mongodb-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using MongoDB. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/mongodb-adapter +``` + +### Use + +```php +$manager = new Manager('mongodb://'.getenv('MONGODB_HOST')); +$collection = MongoDBCachePool::createCollection($manager, 'localhost:27017', 'psr6test.cache'); + +$pool = new MongoDBCachePool($collection); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/MongoDB/composer.json b/vendor/cache/cache/src/Adapter/MongoDB/composer.json new file mode 100644 index 0000000000..6009ae2be0 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/MongoDB/composer.json @@ -0,0 +1,54 @@ +{ + "name": "cache/mongodb-adapter", + "description": "A PSR-6 cache implementation using MongoDB. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "mongodb", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "mongodb/mongodb": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\MongoDB\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/PHPArray/ArrayCachePool.php b/vendor/cache/cache/src/Adapter/PHPArray/ArrayCachePool.php new file mode 100644 index 0000000000..d448e32836 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/PHPArray/ArrayCachePool.php @@ -0,0 +1,206 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\PHPArray; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\CacheItem; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Hierarchy\HierarchicalCachePoolTrait; +use Cache\Hierarchy\HierarchicalPoolInterface; + +/** + * Array cache pool. You could set a limit of how many items you want to be stored to avoid memory leaks. + * + * @author Tobias Nyholm + */ +class ArrayCachePool extends AbstractCachePool implements HierarchicalPoolInterface +{ + use HierarchicalCachePoolTrait; + + /** + * @type PhpCacheItem[] + */ + private $cache; + + /** + * @type array + * A map to hold keys + */ + private $keyMap = []; + + /** + * @type int + * The maximum number of keys in the map + */ + private $limit; + + /** + * @type int + * The next key that we should remove from the cache + */ + private $currentPosition = 0; + + /** + * @param int $limit the amount if items stored in the cache. Using a limit will reduce memory leaks. + * @param array $cache + */ + public function __construct($limit = null, array &$cache = []) + { + $this->cache = &$cache; + $this->limit = $limit; + } + + /** + * {@inheritdoc} + */ + protected function getItemWithoutGenerateCacheKey($key) + { + if (isset($this->deferred[$key])) { + /** @type CacheItem $item */ + $item = clone $this->deferred[$key]; + $item->moveTagsToPrevious(); + + return $item; + } + + return $this->fetchObjectFromCache($key); + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + $keyString = $this->getHierarchyKey($key); + if (!isset($this->cache[$keyString])) { + return [false, null, [], null]; + } + + list($data, $tags, $timestamp) = $this->cache[$keyString]; + + if (is_object($data)) { + $data = clone $data; + } + + return [true, $data, $tags, $timestamp]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + $this->cache = []; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + $this->commit(); + $path = null; + $keyString = $this->getHierarchyKey($key, $path); + if (isset($this->cache[$path])) { + $this->cache[$path]++; + } else { + $this->cache[$path] = 0; + } + $this->clearHierarchyKeyCache(); + + unset($this->cache[$keyString]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $key = $this->getHierarchyKey($item->getKey()); + $value = $item->get(); + if (is_object($value)) { + $value = clone $value; + } + $this->cache[$key] = [$value, $item->getTags(), $item->getExpirationTimestamp()]; + + if ($this->limit !== null) { + // Remove the oldest value + if (isset($this->keyMap[$this->currentPosition])) { + unset($this->cache[$this->keyMap[$this->currentPosition]]); + } + + // Add the new key to the current position + $this->keyMap[$this->currentPosition] = $key; + + // Increase the current position + $this->currentPosition = ($this->currentPosition + 1) % $this->limit; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function getDirectValue($key) + { + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + if (!isset($this->cache[$name])) { + $this->cache[$name] = []; + } + + return $this->cache[$name]; + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + unset($this->cache[$name]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $key) + { + $this->cache[$name][] = $key; + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + foreach ($this->cache[$name] as $i => $item) { + if ($item === $key) { + unset($this->cache[$name][$i]); + } + } + } +} diff --git a/vendor/cache/cache/src/Adapter/PHPArray/Changelog.md b/vendor/cache/cache/src/Adapter/PHPArray/Changelog.md new file mode 100644 index 0000000000..97b45d756b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/PHPArray/Changelog.md @@ -0,0 +1,38 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Fixed + +* Fixed `$path` variable not initialized in `clearOneObjectFromCache`. + +## 0.5.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.4.2 + +### Changed + +* Using `cache/hierarchical-cache:^0.3` + +## 0.4.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/PHPArray/LICENSE b/vendor/cache/cache/src/Adapter/PHPArray/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/PHPArray/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/PHPArray/README.md b/vendor/cache/cache/src/Adapter/PHPArray/README.md new file mode 100644 index 0000000000..e5c89fc0b9 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/PHPArray/README.md @@ -0,0 +1,29 @@ +# Array PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/array-adapter/v/stable)](https://packagist.org/packages/cache/array-adapter) +[![codecov.io](https://codecov.io/github/php-cache/array-adapter/coverage.svg?branch=master)](https://codecov.io/github/array-cache/apc-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/array-adapter/downloads)](https://packagist.org/packages/cache/array-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/array-adapter/d/monthly.png)](https://packagist.org/packages/cache/array-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using PHP array. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/array-adapter +``` + +### Use + +You do not need to do any configuration to use the `ArrayCachePool`. + +```php +$pool = new ArrayCachePool(); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/PHPArray/composer.json b/vendor/cache/cache/src/Adapter/PHPArray/composer.json new file mode 100644 index 0000000000..4ec3fd7d40 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/PHPArray/composer.json @@ -0,0 +1,54 @@ +{ + "name": "cache/array-adapter", + "description": "A PSR-6 cache implementation using a php array. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "array", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "cache/hierarchical-cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\PHPArray\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Predis/Changelog.md b/vendor/cache/cache/src/Adapter/Predis/Changelog.md new file mode 100644 index 0000000000..50e162951d --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Predis/Changelog.md @@ -0,0 +1,45 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Fixed + +* Fixed `$path` variable not initialized in `clearOneObjectFromCache`. + +## 0.5.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.4.2 + +### Changed + +* Rely on `Predis\ClientInterface` instead of `Predis\Client` in `PredisCachePool` + +## 0.4.1 + +### Changed + +* The `PredisCachePool::$cache` is now protected instead of private +* Using `cache/hierarchical-cache:^0.3` + +## 0.4.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Predis/LICENSE b/vendor/cache/cache/src/Adapter/Predis/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Predis/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Predis/PredisCachePool.php b/vendor/cache/cache/src/Adapter/Predis/PredisCachePool.php new file mode 100644 index 0000000000..f1ec6eeee2 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Predis/PredisCachePool.php @@ -0,0 +1,133 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Predis; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Hierarchy\HierarchicalCachePoolTrait; +use Cache\Hierarchy\HierarchicalPoolInterface; +use Predis\ClientInterface as Client; + +/** + * @author Tobias Nyholm + */ +class PredisCachePool extends AbstractCachePool implements HierarchicalPoolInterface +{ + use HierarchicalCachePoolTrait; + + /** + * @type Client + */ + protected $cache; + + /** + * @param Client $cache + */ + public function __construct(Client $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if (false === $result = unserialize($this->cache->get($this->getHierarchyKey($key)))) { + return [false, null, [], null]; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return 'OK' === $this->cache->flushdb()->getPayload(); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + $path = null; + $keyString = $this->getHierarchyKey($key, $path); + if ($path) { + $this->cache->incr($path); + } + $this->clearHierarchyKeyCache(); + + return $this->cache->del($keyString) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + if ($ttl < 0) { + return false; + } + + $key = $this->getHierarchyKey($item->getKey()); + $data = serialize([true, $item->get(), $item->getTags(), $item->getExpirationTimestamp()]); + + if ($ttl === null || $ttl === 0) { + return 'OK' === $this->cache->set($key, $data)->getPayload(); + } + + return 'OK' === $this->cache->setex($key, $ttl, $data)->getPayload(); + } + + /** + * {@inheritdoc} + */ + protected function getDirectValue($key) + { + return $this->cache->get($key); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $value) + { + $this->cache->lpush($name, $value); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + return $this->cache->lrange($name, 0, -1); + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + return $this->cache->del($name); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + return $this->cache->lrem($name, 0, $key); + } +} diff --git a/vendor/cache/cache/src/Adapter/Predis/README.md b/vendor/cache/cache/src/Adapter/Predis/README.md new file mode 100644 index 0000000000..683058ef3a --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Predis/README.md @@ -0,0 +1,33 @@ +# Predis PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/predis-adapter/v/stable)](https://packagist.org/packages/cache/predis-adapter) +[![codecov.io](https://codecov.io/github/php-cache/predis-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/predis-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/predis-adapter/downloads)](https://packagist.org/packages/cache/predis-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/predis-adapter/d/monthly.png)](https://packagist.org/packages/cache/predis-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Predis. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +This implementation is using [Predis](https://github.com/nrk/predis). If you want an adapter with +[PhpRedis](https://github.com/phpredis/phpredis) you should look at our [Redis adapter](https://github.com/php-cache/redis-adapter). + +### Install + +```bash +composer require cache/predis-adapter +``` + +### Use + +To create an instance of `PredisCachePool` you need to configure a `\Predis\Client` object. + +```php +$client = new \Predis\Client('tcp:/127.0.0.1:6379'); +$pool = new PredisCachePool($client); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Predis/composer.json b/vendor/cache/cache/src/Adapter/Predis/composer.json new file mode 100644 index 0000000000..aa9fea250b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Predis/composer.json @@ -0,0 +1,56 @@ +{ + "name": "cache/predis-adapter", + "description": "A PSR-6 cache implementation using Redis (Predis). This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "redis", + "predis", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "cache/hierarchical-cache": "^1.0", + "predis/predis": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Predis\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Redis/Changelog.md b/vendor/cache/cache/src/Adapter/Redis/Changelog.md new file mode 100644 index 0000000000..3893a063ba --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Redis/Changelog.md @@ -0,0 +1,39 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Fixed + +* Fixed `$path` variable not initialized in `clearOneObjectFromCache`. + +## 0.5.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.4.2 + +### Changed + +* The `RedisCachePool::$cache` is now protected instead of private +* Using `cache/hierarchical-cache:^0.3` + +## 0.4.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Redis/LICENSE b/vendor/cache/cache/src/Adapter/Redis/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Redis/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Redis/README.md b/vendor/cache/cache/src/Adapter/Redis/README.md new file mode 100644 index 0000000000..d85e55f4ec --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Redis/README.md @@ -0,0 +1,35 @@ +# Redis PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/redis-adapter/v/stable)](https://packagist.org/packages/cache/redis-adapter) +[![codecov.io](https://codecov.io/github/php-cache/redis-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/redis-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/redis-adapter/downloads)](https://packagist.org/packages/cache/redis-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/redis-adapter/d/monthly.png)](https://packagist.org/packages/cache/redis-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Redis. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +This implementation is using [PhpRedis](https://github.com/phpredis/phpredis). If you want an adapter with +[Predis](https://github.com/nrk/predis) you should look at our [Predis adapter](https://github.com/php-cache/predis-adapter). + +### Install + +```bash +composer require cache/redis-adapter +``` + +### Use + +To create an instance of `RedisCachePool` you need to configure a `\Redis` client. + +```php +$client = new \Redis(); +$client->connect('127.0.0.1', 6379); +$pool = new RedisCachePool($client); +``` + + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Redis/RedisCachePool.php b/vendor/cache/cache/src/Adapter/Redis/RedisCachePool.php new file mode 100644 index 0000000000..1ee038133d --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Redis/RedisCachePool.php @@ -0,0 +1,127 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Redis; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Hierarchy\HierarchicalCachePoolTrait; +use Cache\Hierarchy\HierarchicalPoolInterface; + +/** + * @author Tobias Nyholm + */ +class RedisCachePool extends AbstractCachePool implements HierarchicalPoolInterface +{ + use HierarchicalCachePoolTrait; + + /** + * @type \Redis + */ + protected $cache; + + /** + * @param \Redis $cache + */ + public function __construct(\Redis $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + if (false === $result = unserialize($this->cache->get($this->getHierarchyKey($key)))) { + return [false, null, [], null]; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return $this->cache->flushDb(); + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + $path = null; + $keyString = $this->getHierarchyKey($key, $path); + if ($path) { + $this->cache->incr($path); + } + $this->clearHierarchyKeyCache(); + + return $this->cache->del($keyString) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $key = $this->getHierarchyKey($item->getKey()); + $data = serialize([true, $item->get(), $item->getTags(), $item->getExpirationTimestamp()]); + if ($ttl === null || $ttl === 0) { + return $this->cache->set($key, $data); + } + + return $this->cache->setex($key, $ttl, $data); + } + + /** + * {@inheritdoc} + */ + protected function getDirectValue($key) + { + return $this->cache->get($key); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $value) + { + $this->cache->lPush($name, $value); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + return $this->cache->lRange($name, 0, -1); + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + return $this->cache->del($name); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + return $this->cache->lrem($name, $key, 0); + } +} diff --git a/vendor/cache/cache/src/Adapter/Redis/composer.json b/vendor/cache/cache/src/Adapter/Redis/composer.json new file mode 100644 index 0000000000..b5def50f03 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Redis/composer.json @@ -0,0 +1,58 @@ +{ + "name": "cache/redis-adapter", + "description": "A PSR-6 cache implementation using Redis (PhpRedis). This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "phpredis", + "redis", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "cache/hierarchical-cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "suggest": { + "ext-redis": "The extension required to use this pool." + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Redis\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Adapter/Void/Changelog.md b/vendor/cache/cache/src/Adapter/Void/Changelog.md new file mode 100644 index 0000000000..3834c98ecb --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Void/Changelog.md @@ -0,0 +1,42 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.4.1. + +## 0.4.1 + +### Changed + +- We now support cache/hierarchical-cache: ^0.4 + +## 0.4.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.3.1 + +### Changed + +Updated dependencies + +## 0.3.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Adapter/Void/LICENSE b/vendor/cache/cache/src/Adapter/Void/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Void/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Adapter/Void/README.md b/vendor/cache/cache/src/Adapter/Void/README.md new file mode 100644 index 0000000000..5ef5784061 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Void/README.md @@ -0,0 +1,31 @@ +# Void PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/void-adapter/v/stable)](https://packagist.org/packages/cache/void-adapter) +[![codecov.io](https://codecov.io/github/php-cache/void-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/void-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/void-adapter/downloads)](https://packagist.org/packages/cache/void-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/void-adapter/d/monthly.png)](https://packagist.org/packages/cache/void-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a void implementation of a PSR-6 cache. Other names for this adapter could be Blackhole or Null apdapter. +This adapter does not save anything and will always return an empty CacheItem. It is a part of the PHP Cache +organisation. To read about features like tagging and hierarchy support please read the +shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/void-adapter +``` + +### Use + +You do not need to do any configuration to use the `VoidCachePool`. + +```php +$pool = new VoidCachePool(); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Adapter/Void/VoidCachePool.php b/vendor/cache/cache/src/Adapter/Void/VoidCachePool.php new file mode 100644 index 0000000000..9ea9d16115 --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Void/VoidCachePool.php @@ -0,0 +1,80 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Void; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\PhpCacheItem; +use Cache\Hierarchy\HierarchicalPoolInterface; + +/** + * @author Tobias Nyholm + */ +class VoidCachePool extends AbstractCachePool implements HierarchicalPoolInterface +{ + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + return [false, null, [], null]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function clearTags(array $tags) + { + return true; + } + + protected function getList($name) + { + return []; + } + + protected function removeList($name) + { + return true; + } + + protected function appendListItem($name, $key) + { + } + + protected function removeListItem($name, $key) + { + } +} diff --git a/vendor/cache/cache/src/Adapter/Void/composer.json b/vendor/cache/cache/src/Adapter/Void/composer.json new file mode 100644 index 0000000000..5e353954da --- /dev/null +++ b/vendor/cache/cache/src/Adapter/Void/composer.json @@ -0,0 +1,54 @@ +{ + "name": "cache/void-adapter", + "description": "A PSR-6 cache implementation using Void. This implementation supports tags", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "void", + "tag" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "cache/adapter-common": "^1.0", + "cache/hierarchical-cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "provide": { + "psr/cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Void\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Bridge/Doctrine/Changelog.md b/vendor/cache/cache/src/Bridge/Doctrine/Changelog.md new file mode 100644 index 0000000000..c9888d8293 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/Doctrine/Changelog.md @@ -0,0 +1,20 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + + +## 3.0.1 + +### Changed + +* Bumped versions on dependencies. + +## 3.0.0 + +### Changed + +* Changed Namespace from `Cache\Bridge` to `Cache\Bridge\Doctrine` + +## 2.2.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/Bridge/Doctrine/DoctrineCacheBridge.php b/vendor/cache/cache/src/Bridge/Doctrine/DoctrineCacheBridge.php new file mode 100644 index 0000000000..e4e15f92ff --- /dev/null +++ b/vendor/cache/cache/src/Bridge/Doctrine/DoctrineCacheBridge.php @@ -0,0 +1,149 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Bridge\Doctrine; + +use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Cache\CacheProvider; +use Psr\Cache\CacheItemPoolInterface; + +/** + * This is a bridge between a Doctrine cache and PSR6. + * + * @author Aaron Scherer + */ +class DoctrineCacheBridge extends CacheProvider +{ + /** + * @type CacheItemPoolInterface + */ + private $cachePool; + + /** + * DoctrineCacheBridge constructor. + * + * @param CacheItemPoolInterface $cachePool + */ + public function __construct(CacheItemPoolInterface $cachePool) + { + $this->cachePool = $cachePool; + } + + /** + * @return CacheItemPoolInterface + */ + public function getCachePool() + { + return $this->cachePool; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. + */ + protected function doFetch($id) + { + $item = $this->cachePool->getItem($this->normalizeKey($id)); + + if ($item->isHit()) { + return $item->get(); + } + + return false; + } + + /** + * 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. + */ + protected function doContains($id) + { + return $this->cachePool->hasItem($this->normalizeKey($id)); + } + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $item = $this->cachePool->getItem($this->normalizeKey($id)); + $item->set($data); + + if ($lifeTime !== 0) { + $item->expiresAfter($lifeTime); + } + + return $this->cachePool->save($item); + } + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + protected function doDelete($id) + { + return $this->cachePool->deleteItem($this->normalizeKey($id)); + } + + /** + * Flushes all cache entries. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + protected function doFlush() + { + $this->cachePool->clear(); + } + + /** + * Retrieves cached information from the data store. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + protected function doGetStats() + { + // Not possible, as of yet + } + + /** + * We need to make sure we do not use any characters not supported. + * + * @param string $key + * + * @return string + */ + private function normalizeKey($key) + { + if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { + return preg_replace('|[\{\}\(\)/\\\@\:]|', '_', $key); + } + + return $key; + } +} diff --git a/vendor/cache/cache/src/Bridge/Doctrine/LICENSE b/vendor/cache/cache/src/Bridge/Doctrine/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Bridge/Doctrine/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Bridge/Doctrine/README.md b/vendor/cache/cache/src/Bridge/Doctrine/README.md new file mode 100644 index 0000000000..e6f62f2d11 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/Doctrine/README.md @@ -0,0 +1,39 @@ +# PSR 6 Doctrine Bridge +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/psr-6-doctrine-bridge/v/stable)](https://packagist.org/packages/cache/psr-6-doctrine-bridge) +[![codecov.io](https://codecov.io/github/php-cache/doctrine-bridge/coverage.svg?branch=master)](https://codecov.io/github/php-cache/doctrine-bridge?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/psr-6-doctrine-bridge/downloads)](https://packagist.org/packages/cache/psr-6-doctrine-bridge) +[![Monthly Downloads](https://poser.pugx.org/cache/psr-6-doctrine-bridge/d/monthly.png)](https://packagist.org/packages/cache/psr-6-doctrine-bridge) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This library provides a PSR-6 compliant bridge between Doctrine and a Cache Pool. The bridge implements the +`Doctrine\Common\Cache\Cache` interface. This is useful for projects that require an implementation of +`Doctrine\Common\Cache\Cache`, but you still want to use a PSR-6 implementation. + +### Install + +```bash +composer require cache/psr-6-doctrine-bridge +``` + +### Usage + +```php +use Cache\Bridge\Doctrine\DoctrineCacheBridge; + +// Assuming $pool is an instance of \Psr\Cache\CacheItemPoolInterface +$cacheProvider = new DoctrineCacheBridge($pool); + +$cacheProvider->contains($key); +$cacheProvider->fetch($key); +$cacheProvider->save($key, $value, $ttl); +$cacheProvider->delete($key); + +// Also, if you need it: +$cacheProvider->getPool(); // same as $pool +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Bridge/Doctrine/composer.json b/vendor/cache/cache/src/Bridge/Doctrine/composer.json new file mode 100644 index 0000000000..3724358e79 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/Doctrine/composer.json @@ -0,0 +1,44 @@ +{ + "name": "cache/psr-6-doctrine-bridge", + "type": "library", + "description": "PSR-6 Doctrine bridge", + "keywords": [ + "cache", + "psr-6", + "doctrine" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "doctrine/cache": "^1.6", + "psr/cache-implementation": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "mockery/mockery": "^0.9.9", + "cache/doctrine-adapter": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Bridge\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/Changelog.md b/vendor/cache/cache/src/Bridge/SimpleCache/Changelog.md new file mode 100644 index 0000000000..9868847383 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/Changelog.md @@ -0,0 +1,19 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.1.1 + +## 0.1.1 + +### Fixed + +* Bugs with iterators + +## 0.1.0 + +First release diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/Exception/CacheException.php b/vendor/cache/cache/src/Bridge/SimpleCache/Exception/CacheException.php new file mode 100644 index 0000000000..7da958c352 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/Exception/CacheException.php @@ -0,0 +1,16 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Bridge\SimpleCache\Exception; + +class CacheException extends \RuntimeException implements \Psr\SimpleCache\CacheException +{ +} diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/Exception/InvalidArgumentException.php b/vendor/cache/cache/src/Bridge/SimpleCache/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..4d485ca281 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Bridge\SimpleCache\Exception; + +class InvalidArgumentException extends CacheException implements \Psr\SimpleCache\InvalidArgumentException +{ +} diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/LICENSE b/vendor/cache/cache/src/Bridge/SimpleCache/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/README.md b/vendor/cache/cache/src/Bridge/SimpleCache/README.md new file mode 100644 index 0000000000..3f7ead109c --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/README.md @@ -0,0 +1,30 @@ +# PSR-6 to PSR-16 Bridge (Simple cache) +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/simple-cache-bridge/v/stable)](https://packagist.org/packages/cache/simple-cache-bridge) +[![codecov.io](https://codecov.io/github/php-cache/simple-cache-bridge/coverage.svg?branch=master)](https://codecov.io/github/array-cache/apc-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/simple-cache-bridge/downloads)](https://packagist.org/packages/cache/simple-cache-bridge) +[![Monthly Downloads](https://poser.pugx.org/cache/simple-cache-bridge/d/monthly.png)](https://packagist.org/packages/cache/simple-cache-bridge) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a bridge that converts a PSR-6 cache implementation to PSR-16 (SimpleCache). It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/simple-cache-bridge +``` + +### Use + +You need an existing PSR-6 pool as a cnstructor argument to the bridge. + +```php +$psr6pool = new ArrayCachePool(); +$simpleCache = new SimpleCacheBridge($psr6pool); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/SimpleCacheBridge.php b/vendor/cache/cache/src/Bridge/SimpleCache/SimpleCacheBridge.php new file mode 100644 index 0000000000..a0047beebe --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/SimpleCacheBridge.php @@ -0,0 +1,224 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Bridge\SimpleCache; + +use Cache\Bridge\SimpleCache\Exception\InvalidArgumentException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException as CacheInvalidArgumentException; +use Psr\SimpleCache\CacheInterface; + +/** + * Adds a SimpleCache interface on a PSR-6 cache pool. + * + * @author Magnus Nordlander + */ +class SimpleCacheBridge implements CacheInterface +{ + /** + * @type CacheItemPoolInterface + */ + protected $cacheItemPool; + + /** + * SimpleCacheBridge constructor. + */ + public function __construct(CacheItemPoolInterface $cacheItemPool) + { + $this->cacheItemPool = $cacheItemPool; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + try { + $item = $this->cacheItemPool->getItem($key); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + if (!$item->isHit()) { + return $default; + } + + return $item->get(); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + try { + $item = $this->cacheItemPool->getItem($key); + $item->expiresAfter($ttl); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $item->set($value); + + return $this->cacheItemPool->save($item); + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + try { + return $this->cacheItemPool->deleteItem($key); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->cacheItemPool->clear(); + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + if (!is_array($keys)) { + if (!$keys instanceof \Traversable) { + throw new InvalidArgumentException('$keys is neither an array nor Traversable'); + } + + // Since we need to throw an exception if *any* key is invalid, it doesn't + // make sense to wrap iterators or something like that. + $keys = iterator_to_array($keys, false); + } + + try { + $items = $this->cacheItemPool->getItems($keys); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + return $this->generateValues($default, $items); + } + + /** + * @param $default + * @param $items + * + * @return \Generator + */ + private function generateValues($default, $items) + { + foreach ($items as $key => $item) { + /** @type $item CacheItemInterface */ + if (!$item->isHit()) { + yield $key => $default; + } else { + yield $key => $item->get(); + } + } + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + if (!is_array($values)) { + if (!$values instanceof \Traversable) { + throw new InvalidArgumentException('$values is neither an array nor Traversable'); + } + } + + $keys = []; + $arrayValues = []; + foreach ($values as $key => $value) { + if (is_int($key)) { + $key = (string) $key; + } + + if (!is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', gettype($key))); + } + + if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { + throw new InvalidArgumentException(sprintf('Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:', $key)); + } + + $keys[] = $key; + $arrayValues[$key] = $value; + } + + try { + $items = $this->cacheItemPool->getItems($keys); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $itemSuccess = true; + + foreach ($items as $key => $item) { + /* @var $item CacheItemInterface */ + $item->set($arrayValues[$key]); + + try { + $item->expiresAfter($ttl); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $itemSuccess = $itemSuccess && $this->cacheItemPool->saveDeferred($item); + } + + return $itemSuccess && $this->cacheItemPool->commit(); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + if (!is_array($keys)) { + if (!$keys instanceof \Traversable) { + throw new InvalidArgumentException('$keys is neither an array nor Traversable'); + } + + // Since we need to throw an exception if *any* key is invalid, it doesn't + // make sense to wrap iterators or something like that. + $keys = iterator_to_array($keys, false); + } + + try { + return $this->cacheItemPool->deleteItems($keys); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + try { + return $this->cacheItemPool->hasItem($key); + } catch (CacheInvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/vendor/cache/cache/src/Bridge/SimpleCache/composer.json b/vendor/cache/cache/src/Bridge/SimpleCache/composer.json new file mode 100644 index 0000000000..747c0a9a16 --- /dev/null +++ b/vendor/cache/cache/src/Bridge/SimpleCache/composer.json @@ -0,0 +1,49 @@ +{ + "name": "cache/simple-cache-bridge", + "description": "A PSR-6 bridge to PSR-16. This will make any PSR-6 cache compatible with SimpleCache.", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "psr-16", + "bridge" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Magnus Nordlander", + "email": "magnus@fervo.se", + "homepage": "https://github.com/magnusnordlander" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16", + "mockery/mockery": "^0.9", + "symfony/cache": "^3.2" + }, + "provide": { + "psr/simple-cache-implementation": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Bridge\\SimpleCache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Encryption/Changelog.md b/vendor/cache/cache/src/Encryption/Changelog.md new file mode 100644 index 0000000000..e06e1694b5 --- /dev/null +++ b/vendor/cache/cache/src/Encryption/Changelog.md @@ -0,0 +1,30 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 1.0.0 + +No changes since 0.2.0 + +## 0.2.0 + +### Added + +* Support for `TaggableCacheItemPoolInterface` +* Added `EncryptedCachePool::invalidateTags()` and `EncryptedCachePool::invalidateTag()` +* Added `EncryptedItemDecorator::getCacheItem()` + +### Changed + +* EncryptedCachePool constructor takes a `TaggableCacheItemPoolInterface` instead of a `CacheItemPoolInterface` +* EncryptedItemDecorator constructor takes a `TaggableCacheItemInterface` instead of a `CacheItemInterface` + +### Removed + +* `EncryptedItemDecorator::getExpirationTimestamp()`. +* `EncryptedItemDecorator::getTags()`. Use `EncryptedItemDecorator::getPreviousTags()` +* `EncryptedItemDecorator::addTag()`. Use `EncryptedItemDecorator::setTags()` + +## 0.1.0 + +First release diff --git a/vendor/cache/cache/src/Encryption/EncryptedCachePool.php b/vendor/cache/cache/src/Encryption/EncryptedCachePool.php new file mode 100644 index 0000000000..cf166765ec --- /dev/null +++ b/vendor/cache/cache/src/Encryption/EncryptedCachePool.php @@ -0,0 +1,149 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Encryption; + +use Cache\TagInterop\TaggableCacheItemPoolInterface; +use Defuse\Crypto\Key; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Wraps a CacheItemInterface with EncryptedItemDecorator. + * + * @author Daniel Bannert + */ +class EncryptedCachePool implements TaggableCacheItemPoolInterface +{ + /** + * @type TaggableCacheItemPoolInterface + */ + private $cachePool; + + /** + * @type Key + */ + private $key; + + /** + * @param TaggableCacheItemPoolInterface $cachePool + * @param Key $key + */ + public function __construct(TaggableCacheItemPoolInterface $cachePool, Key $key) + { + $this->cachePool = $cachePool; + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $items = $this->getItems([$key]); + + return reset($items); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + return array_map(function (CacheItemInterface $inner) { + if (!($inner instanceof EncryptedItemDecorator)) { + return new EncryptedItemDecorator($inner, $this->key); + } + + return $inner; + }, $this->cachePool->getItems($keys)); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + return $this->cachePool->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->cachePool->clear(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + return $this->cachePool->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + return $this->cachePool->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof EncryptedItemDecorator) { + throw new InvalidArgumentException('Cache items are not transferable between pools. Item MUST implement EncryptedItemDecorator.'); + } + + return $this->cachePool->save($item->getCacheItem()); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!$item instanceof EncryptedItemDecorator) { + throw new InvalidArgumentException('Cache items are not transferable between pools. Item MUST implement EncryptedItemDecorator.'); + } + + return $this->cachePool->saveDeferred($item->getCacheItem()); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->cachePool->commit(); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + return $this->cachePool->invalidateTags($tags); + } + + /** + * {@inheritdoc} + */ + public function invalidateTag($tag) + { + return $this->cachePool->invalidateTag($tag); + } +} diff --git a/vendor/cache/cache/src/Encryption/EncryptedItemDecorator.php b/vendor/cache/cache/src/Encryption/EncryptedItemDecorator.php new file mode 100644 index 0000000000..205a2612b6 --- /dev/null +++ b/vendor/cache/cache/src/Encryption/EncryptedItemDecorator.php @@ -0,0 +1,168 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Encryption; + +use Cache\TagInterop\TaggableCacheItemInterface; +use Defuse\Crypto\Crypto; +use Defuse\Crypto\Key; + +/** + * Encrypt and Decrypt all the stored items. + * + * @author Daniel Bannert + */ +class EncryptedItemDecorator implements TaggableCacheItemInterface +{ + /** + * The cacheItem should always contain encrypted data. + * + * @type TaggableCacheItemInterface + */ + private $cacheItem; + + /** + * @type Key + */ + private $key; + + /** + * @param TaggableCacheItemInterface $cacheItem + * @param Key $key + */ + public function __construct(TaggableCacheItemInterface $cacheItem, Key $key) + { + $this->cacheItem = $cacheItem; + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->cacheItem->getKey(); + } + + /** + * @return TaggableCacheItemInterface + */ + public function getCacheItem() + { + return $this->cacheItem; + } + + /** + * {@inheritdoc} + */ + public function set($value) + { + $type = gettype($value); + + if ($type === 'object') { + $value = serialize($value); + } + + $json = json_encode(['type' => $type, 'value' => $value]); + + $this->cacheItem->set(Crypto::encrypt($json, $this->key)); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if (!$this->isHit()) { + return; + } + + $item = json_decode(Crypto::decrypt($this->cacheItem->get(), $this->key), true); + + return $this->transform($item); + } + + /** + * {@inheritdoc} + */ + public function isHit() + { + return $this->cacheItem->isHit(); + } + + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + $this->cacheItem->expiresAt($expiration); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + $this->cacheItem->expiresAfter($time); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPreviousTags() + { + return $this->cacheItem->getPreviousTags(); + } + + /** + * {@inheritdoc} + */ + public function setTags(array $tags) + { + $this->cacheItem->setTags($tags); + + return $this; + } + + /** + * Creating a copy of the original CacheItemInterface object. + */ + public function __clone() + { + $this->cacheItem = clone $this->cacheItem; + } + + /** + * Transform value back to it original type. + * + * @param array $item + * + * @return mixed + */ + private function transform(array $item) + { + if ($item['type'] === 'object') { + return unserialize($item['value']); + } + + $value = $item['value']; + + settype($value, $item['type']); + + return $value; + } +} diff --git a/vendor/cache/cache/src/Encryption/LICENSE b/vendor/cache/cache/src/Encryption/LICENSE new file mode 100644 index 0000000000..26e041d07d --- /dev/null +++ b/vendor/cache/cache/src/Encryption/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 - 2016 Aaron Scherer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Encryption/README.md b/vendor/cache/cache/src/Encryption/README.md new file mode 100644 index 0000000000..5de3664a68 --- /dev/null +++ b/vendor/cache/cache/src/Encryption/README.md @@ -0,0 +1,31 @@ +# Encryption PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/encryption-cache/v/stable)](https://packagist.org/packages/cache/encryption-cache) +[![codecov.io](https://codecov.io/github/php-cache/encryption-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/encryption-cache?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/encryption-cache/downloads)](https://packagist.org/packages/cache/encryption-cache) +[![Monthly Downloads](https://poser.pugx.org/cache/encryption-cache/d/monthly.png)](https://packagist.org/packages/cache/encryption-cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository has a encryption wrapper that makes the PSR-6 cache implementation encrypted. + +Encryption and decryption are both expensive operations, and frequent reads from an encrypted data store can quickly become a bottleneck in otherwise performant applications. Use encrypted caches sparingly. + + +### Install + +```bash +composer require cache/encryption-cache +``` + +### Use + +Read the [documentation on usage](http://www.php-cache.com/en/latest/encryption/). + +### Implement + +Read the [documentation on implementation](http://www.php-cache.com/en/latest/implementing-cache-pools/encryption/). + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Encryption/composer.json b/vendor/cache/cache/src/Encryption/composer.json new file mode 100644 index 0000000000..a2fb8c5af1 --- /dev/null +++ b/vendor/cache/cache/src/Encryption/composer.json @@ -0,0 +1,56 @@ +{ + "name": "cache/encryption-cache", + "type": "library", + "description": "Adds encryption support to your PSR-6 cache implementation", + "keywords": [ + "cache", + "psr6", + "encrypted", + "encryption" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Daniel Bannert", + "email": "d.bannert@anolilab.de", + "homepage": "https://github.com/prisis" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "defuse/php-encryption": "^2.0", + "cache/tag-interop": "^1.0", + "psr/cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16", + "cache/array-adapter": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Encryption\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Hierarchy/Changelog.md b/vendor/cache/cache/src/Hierarchy/Changelog.md new file mode 100644 index 0000000000..58431cf980 --- /dev/null +++ b/vendor/cache/cache/src/Hierarchy/Changelog.md @@ -0,0 +1,29 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.4.0 + +## 0.4.0 + +### Changed + +* `HierarchicalCachePoolTrait::getValueFormStore` was renamed to `HierarchicalCachePoolTrait::getDirectValue` + +### Removed + +* Dependency to `cache/taggable-cache`. + +## 0.3.0 + +### Changed + +The `HierarchicalPoolInterface` extends `CacheItemPoolInterface` + +## 0.2.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Hierarchy/HierarchicalCachePoolTrait.php b/vendor/cache/cache/src/Hierarchy/HierarchicalCachePoolTrait.php new file mode 100644 index 0000000000..0a4a4a18f6 --- /dev/null +++ b/vendor/cache/cache/src/Hierarchy/HierarchicalCachePoolTrait.php @@ -0,0 +1,125 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Hierarchy; + +use Cache\Adapter\Common\AbstractCachePool; + +/** + * @author Tobias Nyholm + */ +trait HierarchicalCachePoolTrait +{ + /** + * A temporary cache for keys. + * + * @type array + */ + private $keyCache = []; + + /** + * Get a value from the storage. + * + * @param string $name + * + * @return mixed + */ + abstract public function getDirectValue($name); + + /** + * Get a key to use with the hierarchy. If the key does not start with HierarchicalPoolInterface::SEPARATOR + * this will return an unalterered key. This function supports a tagged key. Ie "foo:bar". + * + * @param string $key The original key + * @param string &$pathKey A cache key for the path. If this key is changed everything beyond that path is changed. + * + * @return string + */ + protected function getHierarchyKey($key, &$pathKey = null) + { + if (!$this->isHierarchyKey($key)) { + return $key; + } + + $key = $this->explodeKey($key); + + $keyString = ''; + // The comments below is for a $key = ["foo!tagHash", "bar!tagHash"] + foreach ($key as $name) { + // 1) $keyString = "foo!tagHash" + // 2) $keyString = "foo!tagHash![foo_index]!bar!tagHash" + $keyString .= $name; + $pathKey = sha1('path'.AbstractCachePool::SEPARATOR_TAG.$keyString); + + if (isset($this->keyCache[$pathKey])) { + $index = $this->keyCache[$pathKey]; + } else { + $index = $this->getDirectValue($pathKey); + $this->keyCache[$pathKey] = $index; + } + + // 1) $keyString = "foo!tagHash![foo_index]!" + // 2) $keyString = "foo!tagHash![foo_index]!bar!tagHash![bar_index]!" + $keyString .= AbstractCachePool::SEPARATOR_TAG.$index.AbstractCachePool::SEPARATOR_TAG; + } + + // Assert: $pathKey = "path!foo!tagHash![foo_index]!bar!tagHash" + // Assert: $keyString = "foo!tagHash![foo_index]!bar!tagHash![bar_index]!" + + // Make sure we do not get awfully long (>250 chars) keys + return sha1($keyString); + } + + /** + * Clear the cache for the keys. + */ + protected function clearHierarchyKeyCache() + { + $this->keyCache = []; + } + + /** + * A hierarchy key MUST begin with the separator. + * + * @param string $key + * + * @return bool + */ + private function isHierarchyKey($key) + { + return substr($key, 0, 1) === HierarchicalPoolInterface::HIERARCHY_SEPARATOR; + } + + /** + * This will take a hierarchy key ("|foo|bar") with tags ("|foo|bar!tagHash") and return an array with + * each level in the hierarchy appended with the tags. ["foo!tagHash", "bar!tagHash"]. + * + * @param string $key + * + * @return array + */ + private function explodeKey($string) + { + list($key, $tag) = explode(AbstractCachePool::SEPARATOR_TAG, $string.AbstractCachePool::SEPARATOR_TAG); + + if ($key === HierarchicalPoolInterface::HIERARCHY_SEPARATOR) { + $parts = ['root']; + } else { + $parts = explode(HierarchicalPoolInterface::HIERARCHY_SEPARATOR, $key); + // remove first element since it is always empty and replace it with 'root' + $parts[0] = 'root'; + } + + return array_map(function ($level) use ($tag) { + return $level.AbstractCachePool::SEPARATOR_TAG.$tag; + }, $parts); + } +} diff --git a/vendor/cache/cache/src/Hierarchy/HierarchicalPoolInterface.php b/vendor/cache/cache/src/Hierarchy/HierarchicalPoolInterface.php new file mode 100644 index 0000000000..4646c66a57 --- /dev/null +++ b/vendor/cache/cache/src/Hierarchy/HierarchicalPoolInterface.php @@ -0,0 +1,24 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Hierarchy; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * Let you use hierarchy if you start your tag key with the HIERARCHY_SEPARATOR. + * + * @author Tobias Nyholm + */ +interface HierarchicalPoolInterface extends CacheItemPoolInterface +{ + const HIERARCHY_SEPARATOR = '|'; +} diff --git a/vendor/cache/cache/src/Hierarchy/LICENSE b/vendor/cache/cache/src/Hierarchy/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Hierarchy/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Hierarchy/README.md b/vendor/cache/cache/src/Hierarchy/README.md new file mode 100644 index 0000000000..63d275e739 --- /dev/null +++ b/vendor/cache/cache/src/Hierarchy/README.md @@ -0,0 +1,35 @@ +# Hierarchical PSR-6 cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/hierarchical-cache/v/stable)](https://packagist.org/packages/cache/hierarchical-cache) +[![codecov.io](https://codecov.io/github/php-cache/hierarchical-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/hierarchical-cache?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/hierarchical-cache/downloads)](https://packagist.org/packages/cache/hierarchical-cache) +[![Monthly Downloads](https://poser.pugx.org/cache/hierarchical-cache/d/monthly.png)](https://packagist.org/packages/cache/hierarchical-cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is an implementation for the PSR-6 for an hierarchical cache architecture. + +If you have a cache key like `|users|:uid|followers|:fid|likes` where `:uid` and `:fid` are arbitrary integers. You + may flush all followers by flushing `|users|:uid|followers`. + +It is a part of the PHP Cache organisation. To read about features like tagging and hierarchy support please read +the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/hierarchical-cache +``` + +### Use + +Read the [documentation on usage](http://www.php-cache.com/en/latest/hierarchy/). + +### Implement + +Read the [documentation on implementation](http://www.php-cache.com/en/latest/implementing-cache-pools/hierarchy/). + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). + diff --git a/vendor/cache/cache/src/Hierarchy/composer.json b/vendor/cache/cache/src/Hierarchy/composer.json new file mode 100644 index 0000000000..fe66eaf96e --- /dev/null +++ b/vendor/cache/cache/src/Hierarchy/composer.json @@ -0,0 +1,48 @@ +{ + "name": "cache/hierarchical-cache", + "description": "A helper trait and interface to your PSR-6 cache to support hierarchical keys.", + "type": "library", + "license": "MIT", + "keywords": [ + "cache", + "psr-6", + "hierarchy", + "hierarchical" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "cache/adapter-common": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21" + }, + "autoload": { + "psr-4": { + "Cache\\Hierarchy\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Namespaced/Changelog.md b/vendor/cache/cache/src/Namespaced/Changelog.md new file mode 100644 index 0000000000..45a2b4ab33 --- /dev/null +++ b/vendor/cache/cache/src/Namespaced/Changelog.md @@ -0,0 +1,34 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Added + +* NamespacedCachePool implements HierarchicalPoolInterface + +## 0.1.3 + +### Changed + +* Updated dependencies + +## 0.1.2 + +### Fixed + +* Typos, documentation and general package improvements. + +## 0.1.1 + +### Changed + +* Updated type hints for the cache pool. +* Using `cache/hierarchical-cache:^0.3` + +## 0.1.0 + +First release diff --git a/vendor/cache/cache/src/Namespaced/LICENSE b/vendor/cache/cache/src/Namespaced/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Namespaced/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Namespaced/NamespacedCachePool.php b/vendor/cache/cache/src/Namespaced/NamespacedCachePool.php new file mode 100644 index 0000000000..d7c85c8378 --- /dev/null +++ b/vendor/cache/cache/src/Namespaced/NamespacedCachePool.php @@ -0,0 +1,147 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Namespaced; + +use Cache\Hierarchy\HierarchicalPoolInterface; +use Psr\Cache\CacheItemInterface; + +/** + * Prefix all the stored items with a namespace. Also make sure you can clear all items + * in that namespace. + * + * @author Tobias Nyholm + */ +class NamespacedCachePool implements HierarchicalPoolInterface +{ + /** + * @type HierarchicalPoolInterface + */ + private $cachePool; + + /** + * @type string + */ + private $namespace; + + /** + * @param HierarchicalPoolInterface $cachePool + * @param string $namespace + */ + public function __construct(HierarchicalPoolInterface $cachePool, $namespace) + { + $this->cachePool = $cachePool; + $this->namespace = $namespace; + } + + /** + * Add namespace prefix on the key. + * + * @param array $keys + */ + private function prefixValue(&$key) + { + // |namespace|key + $key = HierarchicalPoolInterface::HIERARCHY_SEPARATOR.$this->namespace.HierarchicalPoolInterface::HIERARCHY_SEPARATOR.$key; + } + + /** + * @param array $keys + */ + private function prefixValues(array &$keys) + { + foreach ($keys as &$key) { + $this->prefixValue($key); + } + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $this->prefixValue($key); + + return $this->cachePool->getItem($key); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $this->prefixValues($keys); + + return $this->cachePool->getItems($keys); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + $this->prefixValue($key); + + return $this->cachePool->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->cachePool->deleteItem(HierarchicalPoolInterface::HIERARCHY_SEPARATOR.$this->namespace); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + $this->prefixValue($key); + + return $this->cachePool->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $this->prefixValues($keys); + + return $this->cachePool->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + return $this->cachePool->save($item); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->cachePool->saveDeferred($item); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->cachePool->commit(); + } +} diff --git a/vendor/cache/cache/src/Namespaced/README.md b/vendor/cache/cache/src/Namespaced/README.md new file mode 100644 index 0000000000..cbaeb2cd32 --- /dev/null +++ b/vendor/cache/cache/src/Namespaced/README.md @@ -0,0 +1,31 @@ +# Namespaced PSR-6 cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/namespaced-cache/v/stable)](https://packagist.org/packages/cache/namespaced-cache) +[![codecov.io](https://codecov.io/github/php-cache/namespaced-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/namespaced-cache?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/namespaced-cache/downloads)](https://packagist.org/packages/cache/namespaced-cache) +[![Monthly Downloads](https://poser.pugx.org/cache/namespaced-cache/d/monthly.png)](https://packagist.org/packages/cache/namespaced-cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a decorator for a PSR-6 hierarchical cache. It will allow you to use namespaces. + +It is a part of the PHP Cache organisation. To read about features like tagging and hierarchy support please read +the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/namespaced-cache +``` + +### Use + +```php +$hierarchyPool = new RedisCachePool($client); +$namespacedPool = new NamespacedCachePool($hierarchyPool, 'acme'); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). + diff --git a/vendor/cache/cache/src/Namespaced/composer.json b/vendor/cache/cache/src/Namespaced/composer.json new file mode 100644 index 0000000000..3e5a52d587 --- /dev/null +++ b/vendor/cache/cache/src/Namespaced/composer.json @@ -0,0 +1,43 @@ +{ + "name": "cache/namespaced-cache", + "description": "A decorator that makes your cache support namespaces", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "namespace" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "cache/hierarchical-cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/memcached-adapter": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\Namespaced\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Prefixed/Changelog.md b/vendor/cache/cache/src/Prefixed/Changelog.md new file mode 100644 index 0000000000..73149d9d04 --- /dev/null +++ b/vendor/cache/cache/src/Prefixed/Changelog.md @@ -0,0 +1,25 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Removed + +- Dependency on `cache/hierarchical-cache` + +## 0.1.2 + +### Changed + +- We now support cache/hierarchical-cache: ^0.4 + +## 0.1.1 + +### Fixed + +- Typos, documentation and general package improvements. + + diff --git a/vendor/cache/cache/src/Prefixed/LICENSE b/vendor/cache/cache/src/Prefixed/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Prefixed/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Prefixed/PrefixedCachePool.php b/vendor/cache/cache/src/Prefixed/PrefixedCachePool.php new file mode 100644 index 0000000000..ca8b87df49 --- /dev/null +++ b/vendor/cache/cache/src/Prefixed/PrefixedCachePool.php @@ -0,0 +1,145 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Prefixed; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; + +/** + * Prefix all cache items with a string. + * + * @author Tobias Nyholm + */ +class PrefixedCachePool implements CacheItemPoolInterface +{ + /** + * @type CacheItemPoolInterface + */ + private $cachePool; + + /** + * @type string + */ + private $prefix; + + /** + * @param CacheItemPoolInterface $cachePool + * @param string $prefix + */ + public function __construct(CacheItemPoolInterface $cachePool, $prefix) + { + $this->cachePool = $cachePool; + $this->prefix = $prefix; + } + + /** + * Add namespace prefix on the key. + * + * @param array $keys + */ + private function prefixValue(&$key) + { + $key = $this->prefix.$key; + } + + /** + * @param array $keys + */ + private function prefixValues(array &$keys) + { + foreach ($keys as &$key) { + $this->prefixValue($key); + } + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $this->prefixValue($key); + + return $this->cachePool->getItem($key); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $this->prefixValues($keys); + + return $this->cachePool->getItems($keys); + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + $this->prefixValue($key); + + return $this->cachePool->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->cachePool->clear(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + $this->prefixValue($key); + + return $this->cachePool->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $this->prefixValues($keys); + + return $this->cachePool->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + return $this->cachePool->save($item); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->cachePool->saveDeferred($item); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->cachePool->commit(); + } +} diff --git a/vendor/cache/cache/src/Prefixed/README.md b/vendor/cache/cache/src/Prefixed/README.md new file mode 100644 index 0000000000..0c3503d4ee --- /dev/null +++ b/vendor/cache/cache/src/Prefixed/README.md @@ -0,0 +1,31 @@ +# Prefixed PSR-6 cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/prefixed-cache/v/stable)](https://packagist.org/packages/cache/prefixed-cache) +[![codecov.io](https://codecov.io/github/php-cache/prefixed-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/prefixed-cache?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/prefixed-cache/downloads)](https://packagist.org/packages/cache/prefixed-cache) +[![Monthly Downloads](https://poser.pugx.org/cache/prefixed-cache/d/monthly.png)](https://packagist.org/packages/cache/prefixed-cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a decorator for a PSR-6 cache. It will allow you to prefix all cache items with a predefined key. + +It is a part of the PHP Cache organisation. To read about features like tagging and hierarchy support please read +the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/prefixed-cache +``` + +### Use + +```php +$anyPSR6Pool = new RedisCachePool($client); +$prefixedPool = new PrefixedCachePool($anyPSR6Pool, 'acme'); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). + diff --git a/vendor/cache/cache/src/Prefixed/composer.json b/vendor/cache/cache/src/Prefixed/composer.json new file mode 100644 index 0000000000..569b4bad85 --- /dev/null +++ b/vendor/cache/cache/src/Prefixed/composer.json @@ -0,0 +1,42 @@ +{ + "name": "cache/prefixed-cache", + "description": "A decorator that makes your cache support prefix", + "type": "library", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "keywords": [ + "cache", + "psr-6", + "prefix" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16" + }, + "autoload": { + "psr-4": { + "Cache\\Prefixed\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/SessionHandler/Changelog.md b/vendor/cache/cache/src/SessionHandler/Changelog.md new file mode 100644 index 0000000000..77c8b2b4df --- /dev/null +++ b/vendor/cache/cache/src/SessionHandler/Changelog.md @@ -0,0 +1,19 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +No changes since 0.2.1 + +## 0.2.1 + +### Fixed + +Typos, documentation and general package improvements. + +## 0.2.0 + +No changelog before this version diff --git a/vendor/cache/cache/src/SessionHandler/LICENSE b/vendor/cache/cache/src/SessionHandler/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/SessionHandler/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/SessionHandler/Psr6SessionHandler.php b/vendor/cache/cache/src/SessionHandler/Psr6SessionHandler.php new file mode 100644 index 0000000000..4938128d84 --- /dev/null +++ b/vendor/cache/cache/src/SessionHandler/Psr6SessionHandler.php @@ -0,0 +1,119 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\SessionHandler; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Aaron Scherer + */ +class Psr6SessionHandler implements \SessionHandlerInterface +{ + /** + * @type CacheItemPoolInterface + */ + private $cache; + + /** + * @type int Time to live in seconds + */ + private $ttl; + + /** + * @type string Key prefix for shared environments. + */ + private $prefix; + + /** + * @param CacheItemPoolInterface $cache + * @param array $options { + * + * @type int $ttl The time to live in seconds + * @type string $prefix The prefix to use for the cache keys in order to avoid collision + * } + */ + public function __construct(CacheItemPoolInterface $cache, array $options = []) + { + $this->cache = $cache; + + $this->ttl = isset($options['ttl']) ? (int) $options['ttl'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'psr6ses_'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $item = $this->getCacheItem($sessionId); + if ($item->isHit()) { + return $item->get(); + } + + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $item = $this->getCacheItem($sessionId); + $item->set($data) + ->expiresAfter($this->ttl); + + return $this->cache->save($item); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->cache->deleteItem($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($lifetime) + { + // not required here because cache will auto expire the records anyhow. + return true; + } + + /** + * @param $sessionId + * + * @return \Psr\Cache\CacheItemInterface + */ + private function getCacheItem($sessionId) + { + return $this->cache->getItem($this->prefix.$sessionId); + } +} diff --git a/vendor/cache/cache/src/SessionHandler/README.md b/vendor/cache/cache/src/SessionHandler/README.md new file mode 100644 index 0000000000..9500db8d5f --- /dev/null +++ b/vendor/cache/cache/src/SessionHandler/README.md @@ -0,0 +1,33 @@ +# PSR-6 Session handler +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/session-handler/v/stable)](https://packagist.org/packages/cache/session-handler) +[![codecov.io](https://codecov.io/github/php-cache/session-handler/coverage.svg?branch=master)](https://codecov.io/github/php-cache/session-handler?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/session-handler/downloads)](https://packagist.org/packages/cache/session-handler) +[![Monthly Downloads](https://poser.pugx.org/cache/session-handler/d/monthly.png)](https://packagist.org/packages/cache/session-handler) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PHP session handler that supports PSR-6 cache. It is a part of the PHP Cache organisation. Find more +documentation at [www.php-cache.com](http://www.php-cache.com). + + +### Install + +```bash +composer require cache/session-handler +``` + +### Use + +```php +$pool = new ArrayCachePool(); +$config = ['ttl'=>3600, 'prefix'=>'foobar']; + +$sessionHandler = new Psr6SessionHandler($pool, $config); +``` + +Note that this session handler does no kind of locking, so it will lose or overwrite your session data if you run scripts concurrently. You have been warned. + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/SessionHandler/composer.json b/vendor/cache/cache/src/SessionHandler/composer.json new file mode 100644 index 0000000000..7cce79a073 --- /dev/null +++ b/vendor/cache/cache/src/SessionHandler/composer.json @@ -0,0 +1,47 @@ +{ + "name": "cache/session-handler", + "type": "library", + "description": "An implementation of PHP's SessionHandlerInterface that allows PSR-6", + "keywords": [ + "cache", + "psr-6", + "session handler" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/array-adapter": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\SessionHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/TagInterop/.github/PULL_REQUEST_TEMPLATE.md b/vendor/cache/cache/src/TagInterop/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..4a339b4c2f --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/php-cache/cache + +Thank you for contributing. diff --git a/vendor/cache/cache/src/TagInterop/.travis.yml b/vendor/cache/cache/src/TagInterop/.travis.yml new file mode 100644 index 0000000000..942fe27e94 --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/.travis.yml @@ -0,0 +1,22 @@ +language: php +sudo: false + +matrix: + include: + - php: 7.0 + +cache: + directories: + - "$HOME/.composer/cache" + +install: + - composer update --prefer-dist --prefer-stable + +script: + - ./vendor/bin/phpunit --coverage-clover=coverage.xml + +after_success: + - pip install --user codecov && codecov + +notifications: + email: false diff --git a/vendor/cache/cache/src/TagInterop/Changelog.md b/vendor/cache/cache/src/TagInterop/Changelog.md new file mode 100644 index 0000000000..1596519c3c --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/Changelog.md @@ -0,0 +1,9 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 1.0.0 + +First release + + diff --git a/vendor/cache/cache/src/TagInterop/LICENSE b/vendor/cache/cache/src/TagInterop/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/TagInterop/README.md b/vendor/cache/cache/src/TagInterop/README.md new file mode 100644 index 0000000000..28511c91db --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/README.md @@ -0,0 +1,25 @@ +# Tag support for PSR-6 Cache +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/tag-interop/v/stable)](https://packagist.org/packages/cache/tag-interop) +[![Total Downloads](https://poser.pugx.org/cache/tag-interop/downloads)](https://packagist.org/packages/cache/tag-interop) +[![Monthly Downloads](https://poser.pugx.org/cache/tag-interop/d/monthly.png)](https://packagist.org/packages/cache/tag-interop) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository holds two interfaces for tagging. These interfaces will make their +way into PHP Fig. Representatives from Symfony, PHP-cache and Drupal has worked +together to agree on these interfaces. + +### Install + +```bash +composer require cache/tag-interop +``` + +### Use + +Read the [documentation on usage](http://www.php-cache.com/). + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/TagInterop/TaggableCacheItemInterface.php b/vendor/cache/cache/src/TagInterop/TaggableCacheItemInterface.php new file mode 100644 index 0000000000..5823b0bb2b --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/TaggableCacheItemInterface.php @@ -0,0 +1,43 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\TagInterop; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * An item that supports tags. This interface is a soon-to-be-PSR. + * + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +interface TaggableCacheItemInterface extends CacheItemInterface +{ + /** + * Get all existing tags. These are the tags the item has when the item is + * returned from the pool. + * + * @return array + */ + public function getPreviousTags(); + + /** + * Overwrite all tags with a new set of tags. + * + * @param string[] $tags An array of tags + * + * @throws InvalidArgumentException When a tag is not valid. + * + * @return TaggableCacheItemInterface + */ + public function setTags(array $tags); +} diff --git a/vendor/cache/cache/src/TagInterop/TaggableCacheItemPoolInterface.php b/vendor/cache/cache/src/TagInterop/TaggableCacheItemPoolInterface.php new file mode 100644 index 0000000000..055bf4b09d --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/TaggableCacheItemPoolInterface.php @@ -0,0 +1,60 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\TagInterop; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Interface for invalidating cached items using tags. This interface is a soon-to-be-PSR. + * + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +interface TaggableCacheItemPoolInterface extends CacheItemPoolInterface +{ + /** + * Invalidates cached items using a tag. + * + * @param string $tag The tag to invalidate + * + * @throws InvalidArgumentException When $tags is not valid + * + * @return bool True on success + */ + public function invalidateTag($tag); + + /** + * Invalidates cached items using tags. + * + * @param string[] $tags An array of tags to invalidate + * + * @throws InvalidArgumentException When $tags is not valid + * + * @return bool True on success + */ + public function invalidateTags(array $tags); + + /** + * {@inheritdoc} + * + * @return TaggableCacheItemInterface + */ + public function getItem($key); + + /** + * {@inheritdoc} + * + * @return array|\Traversable|TaggableCacheItemInterface[] + */ + public function getItems(array $keys = []); +} diff --git a/vendor/cache/cache/src/TagInterop/composer.json b/vendor/cache/cache/src/TagInterop/composer.json new file mode 100644 index 0000000000..03bc1e50eb --- /dev/null +++ b/vendor/cache/cache/src/TagInterop/composer.json @@ -0,0 +1,39 @@ +{ + "name": "cache/tag-interop", + "type": "library", + "description": "Framework interoperable interfaces for tags", + "keywords": [ + "cache", + "psr6", + "tag", + "psr" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Nicolas Grekas ", + "email": "p@tchwork.com", + "homepage": "https://github.com/nicolas-grekas" + } + ], + "require": { + "php": "^5.5 || ^7.0", + "psr/cache": "^1.0" + }, + "autoload": { + "psr-4": { + "Cache\\TagInterop\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + } +} diff --git a/vendor/cache/cache/src/Taggable/Changelog.md b/vendor/cache/cache/src/Taggable/Changelog.md new file mode 100644 index 0000000000..da23421b97 --- /dev/null +++ b/vendor/cache/cache/src/Taggable/Changelog.md @@ -0,0 +1,78 @@ +# Changelog + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.0.0 + +### Added + +* `Cache\Taggable\Exception\InvalidArgumentException` + +### Changed + +* We do not throw `Cache\Adapter\Common\Exception\InvalidArgumentException` anymore. Instead we throw +`Cache\Taggable\Exception\InvalidArgumentException`. Both exceptions do implement `Psr\Cache\InvalidArgumentException` +* We do not require `cache/adapter-common` + +### Removed + +* Deprecated interfaces `TaggableItemInterface` and `TaggablePoolInterface` + +## 0.5.1 + +### Fixed + +* Bug on `TaggablePSR6ItemAdapter::isItemCreatedHere` where item value was `null`. + +## 0.5.0 + +### Added + +* Support for `TaggableCacheItemPoolInterface` + +### Changed + +* The behavior of `TaggablePSR6ItemAdapter::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `TaggablePoolTrait` +* Deprecated `TaggablePoolInterface` in favor of `Cache\TagInterop\TaggableCacheItemPoolInterface` +* Deprecated `TaggableItemInterface` in favor of `Cache\TagInterop\TaggableCacheItemInterface` +* Removed support for `TaggablePoolInterface` and `TaggableItemInterface` +* `TaggablePSR6ItemAdapter::getTags()`. Use `TaggablePSR6ItemAdapter::getPreviousTags()` +* `TaggablePSR6ItemAdapter::addTag()`. Use `TaggablePSR6ItemAdapter::setTags()` + +## 0.4.3 + +### Fixed + +* Do not lose the data when you start using the `TaggablePSR6PoolAdapter` + +## 0.4.2 + +### Changed + +* Updated version for integration tests +* Made `TaggablePSR6PoolAdapter::getTags` protected instead of private + +## 0.4.1 + +### Fixed + +* Saving an expired value should be the same as removing that value + +## 0.4.0 + +This is a big BC break. The API is rewritten and how we store tags has changed. Each tag is a key to a list in the +cache storage. The list contains keys to items that uses that tag. + +* The `TaggableItemInterface` is completely rewritten. It extends `CacheItemInterface` and has three methods: `getTags`, `setTags` and `addTag`. +* The `TaggablePoolInterface` is also rewritten. It has a new `clearTags` function. +* The `TaggablePoolTrait` has new methods to manipulate the list of tags. + +## 0.3.1 + +No changelog before this version diff --git a/vendor/cache/cache/src/Taggable/Exception/InvalidArgumentException.php b/vendor/cache/cache/src/Taggable/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..bcfa342e41 --- /dev/null +++ b/vendor/cache/cache/src/Taggable/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Taggable\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements \Psr\Cache\InvalidArgumentException +{ +} diff --git a/vendor/cache/cache/src/Taggable/LICENSE b/vendor/cache/cache/src/Taggable/LICENSE new file mode 100644 index 0000000000..82f8feef6b --- /dev/null +++ b/vendor/cache/cache/src/Taggable/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/cache/src/Taggable/README.md b/vendor/cache/cache/src/Taggable/README.md new file mode 100644 index 0000000000..13f287ddc4 --- /dev/null +++ b/vendor/cache/cache/src/Taggable/README.md @@ -0,0 +1,22 @@ +# Taggable PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/taggable-cache/v/stable)](https://packagist.org/packages/cache/taggable-cache) +[![codecov.io](https://codecov.io/github/php-cache/taggable-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/taggable-cache?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/taggable-cache/downloads)](https://packagist.org/packages/cache/taggable-cache) +[![Monthly Downloads](https://poser.pugx.org/cache/taggable-cache/d/monthly.png)](https://packagist.org/packages/cache/taggable-cache) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository has a adapter that makes any PSR-6 pool taggable. All PHPC ache pools support tagging out of the box. +Using tags allow you to tag related items, and then clear the cached data for that tag only. It is a part of the PHP Cache +organisation. To read about features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Install + +```bash +composer require cache/taggable-cache +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/cache/src/Taggable/TaggablePSR6ItemAdapter.php b/vendor/cache/cache/src/Taggable/TaggablePSR6ItemAdapter.php new file mode 100644 index 0000000000..1147124aee --- /dev/null +++ b/vendor/cache/cache/src/Taggable/TaggablePSR6ItemAdapter.php @@ -0,0 +1,235 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Taggable; + +use Cache\Taggable\Exception\InvalidArgumentException; +use Cache\TagInterop\TaggableCacheItemInterface; +use Psr\Cache\CacheItemInterface; + +/** + * @internal + * + * An adapter for non-taggable cache items, to be used with the cache pool + * adapter. + * + * This adapter stores tags along with the cached value, by storing wrapping + * the item in an array structure containing both + * + * @author Magnus Nordlander + */ +class TaggablePSR6ItemAdapter implements TaggableCacheItemInterface +{ + /** + * @type bool + */ + private $initialized = false; + + /** + * @type CacheItemInterface + */ + private $cacheItem; + + /** + * @type array + */ + private $prevTags = []; + + /** + * @type array + */ + private $tags = []; + + /** + * @param CacheItemInterface $cacheItem + */ + private function __construct(CacheItemInterface $cacheItem) + { + $this->cacheItem = $cacheItem; + } + + /** + * @param CacheItemInterface $cacheItem + * + * @return TaggablePSR6ItemAdapter + */ + public static function makeTaggable(CacheItemInterface $cacheItem) + { + return new self($cacheItem); + } + + /** + * @return CacheItemInterface + */ + public function unwrap() + { + return $this->cacheItem; + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->cacheItem->getKey(); + } + + /** + * {@inheritdoc} + */ + public function get() + { + $rawItem = $this->cacheItem->get(); + + // If it is a cache item we created + if ($this->isItemCreatedHere($rawItem)) { + return $rawItem['value']; + } + + // This is an item stored before we used this fake cache + return $rawItem; + } + + /** + * {@inheritdoc} + */ + public function isHit() + { + return $this->cacheItem->isHit(); + } + + /** + * {@inheritdoc} + */ + public function set($value) + { + $this->initializeTags(); + + $this->cacheItem->set([ + 'value' => $value, + 'tags' => $this->tags, + ]); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPreviousTags() + { + $this->initializeTags(); + + return $this->prevTags; + } + + /** + * {@inheritdoc} + */ + public function getTags() + { + return $this->tags; + } + + /** + * {@inheritdoc} + */ + public function setTags(array $tags) + { + $this->tags = []; + + return $this->tag($tags); + } + + private function tag($tags) + { + if (!is_array($tags)) { + $tags = [$tags]; + } + + $this->initializeTags(); + + foreach ($tags as $tag) { + if (!is_string($tag)) { + throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag))); + } + if (isset($this->tags[$tag])) { + continue; + } + if (!isset($tag[0])) { + throw new InvalidArgumentException('Cache tag length must be greater than zero'); + } + if (isset($tag[strcspn($tag, '{}()/\@:')])) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag)); + } + $this->tags[$tag] = $tag; + } + + $this->updateTags(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + $this->cacheItem->expiresAt($expiration); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + $this->cacheItem->expiresAfter($time); + + return $this; + } + + private function updateTags() + { + $this->cacheItem->set([ + 'value' => $this->get(), + 'tags' => $this->tags, + ]); + } + + private function initializeTags() + { + if (!$this->initialized) { + if ($this->cacheItem->isHit()) { + $rawItem = $this->cacheItem->get(); + + if ($this->isItemCreatedHere($rawItem)) { + $this->prevTags = $rawItem['tags']; + } + } + + $this->initialized = true; + } + } + + /** + * Verify that the raw data is a cache item created by this class. + * + * @param mixed $rawItem + * + * @return bool + */ + private function isItemCreatedHere($rawItem) + { + return is_array($rawItem) && array_key_exists('value', $rawItem) && array_key_exists('tags', $rawItem) && count($rawItem) === 2; + } +} diff --git a/vendor/cache/cache/src/Taggable/TaggablePSR6PoolAdapter.php b/vendor/cache/cache/src/Taggable/TaggablePSR6PoolAdapter.php new file mode 100644 index 0000000000..3c784f4f8a --- /dev/null +++ b/vendor/cache/cache/src/Taggable/TaggablePSR6PoolAdapter.php @@ -0,0 +1,307 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Taggable; + +use Cache\TagInterop\TaggableCacheItemInterface; +use Cache\TagInterop\TaggableCacheItemPoolInterface; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; + +/** + * This adapter lets you make any PSR-6 cache pool taggable. If a pool is + * already taggable, it is simply returned by makeTaggable. Tags are stored + * either in the same cache pool, or a a separate pool, and both of these + * appoaches come with different caveats. + * + * A general caveat is that using this adapter reserves any cache key starting + * with '__tag.'. + * + * Using the same pool is precarious if your cache does LRU evictions of items + * even if they do not expire (as in e.g. memcached). If so, the tag item may + * be evicted without all of the tagged items having been evicted first, + * causing items to lose their tags. + * + * In order to mitigate this issue, you may use a separate, more persistent + * pool for your tag items. Do however note that if you are doing so, the + * entire pool is reserved for tags, as this pool is cleared whenever the + * main pool is cleared. + * + * @author Magnus Nordlander + */ +class TaggablePSR6PoolAdapter implements TaggableCacheItemPoolInterface +{ + /** + * @type CacheItemPoolInterface + */ + private $cachePool; + + /** + * @type CacheItemPoolInterface + */ + private $tagStorePool; + + /** + * @param CacheItemPoolInterface $cachePool + * @param CacheItemPoolInterface $tagStorePool + */ + private function __construct(CacheItemPoolInterface $cachePool, CacheItemPoolInterface $tagStorePool = null) + { + $this->cachePool = $cachePool; + if ($tagStorePool) { + $this->tagStorePool = $tagStorePool; + } else { + $this->tagStorePool = $cachePool; + } + } + + /** + * @param CacheItemPoolInterface $cachePool The pool to which to add tagging capabilities + * @param CacheItemPoolInterface|null $tagStorePool The pool to store tags in. If null is passed, the main pool is used + * + * @return TaggableCacheItemPoolInterface + */ + public static function makeTaggable(CacheItemPoolInterface $cachePool, CacheItemPoolInterface $tagStorePool = null) + { + if ($cachePool instanceof TaggableCacheItemPoolInterface && $tagStorePool === null) { + return $cachePool; + } + + return new self($cachePool, $tagStorePool); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + return TaggablePSR6ItemAdapter::makeTaggable($this->cachePool->getItem($key)); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $items = $this->cachePool->getItems($keys); + + $wrappedItems = []; + foreach ($items as $key => $item) { + $wrappedItems[$key] = TaggablePSR6ItemAdapter::makeTaggable($item); + } + + return $wrappedItems; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + return $this->cachePool->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $ret = $this->cachePool->clear(); + + return $this->tagStorePool->clear() && $ret; // Is this acceptable? + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + $this->preRemoveItem($key); + + return $this->cachePool->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + $this->preRemoveItem($key); + } + + return $this->cachePool->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + $this->removeTagEntries($item); + $this->saveTags($item); + + return $this->cachePool->save($item->unwrap()); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + $this->saveTags($item); + + return $this->cachePool->saveDeferred($item->unwrap()); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + $this->tagStorePool->commit(); + $this->cachePool->commit(); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $value) + { + $listItem = $this->tagStorePool->getItem($name); + if (!is_array($list = $listItem->get())) { + $list = []; + } + + $list[] = $value; + $listItem->set($list); + $this->tagStorePool->save($listItem); + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + return $this->tagStorePool->deleteItem($name); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $listItem = $this->tagStorePool->getItem($name); + if (!is_array($list = $listItem->get())) { + $list = []; + } + + $list = array_filter($list, function ($value) use ($key) { + return $value !== $key; + }); + + $listItem->set($list); + $this->tagStorePool->save($listItem); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + $listItem = $this->tagStorePool->getItem($name); + if (!is_array($list = $listItem->get())) { + $list = []; + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function getTagKey($tag) + { + return '__tag.'.$tag; + } + + /** + * @param TaggablePSR6ItemAdapter $item + * + * @return $this + */ + private function saveTags(TaggablePSR6ItemAdapter $item) + { + $tags = $item->getTags(); + foreach ($tags as $tag) { + $this->appendListItem($this->getTagKey($tag), $item->getKey()); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) + { + $itemIds = []; + foreach ($tags as $tag) { + $itemIds = array_merge($itemIds, $this->getList($this->getTagKey($tag))); + } + + // Remove all items with the tag + $success = $this->deleteItems($itemIds); + + if ($success) { + // Remove the tag list + foreach ($tags as $tag) { + $this->removeList($this->getTagKey($tag)); + } + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function invalidateTag($tag) + { + return $this->invalidateTags([$tag]); + } + + /** + * Removes the key form all tag lists. + * + * @param string $key + * + * @return $this + */ + private function preRemoveItem($key) + { + $item = $this->getItem($key); + $this->removeTagEntries($item); + + return $this; + } + + /** + * @param TaggableCacheItemInterface $item + */ + private function removeTagEntries($item) + { + $tags = $item->getPreviousTags(); + foreach ($tags as $tag) { + $this->removeListItem($this->getTagKey($tag), $item->getKey()); + } + } +} diff --git a/vendor/cache/cache/src/Taggable/composer.json b/vendor/cache/cache/src/Taggable/composer.json new file mode 100644 index 0000000000..fded8de5c0 --- /dev/null +++ b/vendor/cache/cache/src/Taggable/composer.json @@ -0,0 +1,55 @@ +{ + "name": "cache/taggable-cache", + "type": "library", + "description": "Add tag support to your PSR-6 cache implementation", + "keywords": [ + "cache", + "psr6", + "tag", + "taggable" + ], + "homepage": "http://www.php-cache.com/en/latest/", + "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Magnus Nordlander", + "email": "magnus@fervo.se", + "homepage": "https://github.com/magnusnordlander" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "cache/tag-interop": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21", + "cache/integration-tests": "^0.16", + "symfony/cache": "^3.1" + }, + "autoload": { + "psr-4": { + "Cache\\Taggable\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000000..2c72175e77 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000000..f27399a042 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000000..bc91e341ad --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,2620 @@ + $vendorDir . '/cache/cache/src/Adapter/Apc/ApcCachePool.php', + 'Cache\\Adapter\\Apcu\\ApcuCachePool' => $vendorDir . '/cache/cache/src/Adapter/Apcu/ApcuCachePool.php', + 'Cache\\Adapter\\Chain\\CachePoolChain' => $vendorDir . '/cache/cache/src/Adapter/Chain/CachePoolChain.php', + 'Cache\\Adapter\\Chain\\Exception\\NoPoolAvailableException' => $vendorDir . '/cache/cache/src/Adapter/Chain/Exception/NoPoolAvailableException.php', + 'Cache\\Adapter\\Chain\\Exception\\PoolFailedException' => $vendorDir . '/cache/cache/src/Adapter/Chain/Exception/PoolFailedException.php', + 'Cache\\Adapter\\Common\\AbstractCachePool' => $vendorDir . '/cache/cache/src/Adapter/Common/AbstractCachePool.php', + 'Cache\\Adapter\\Common\\CacheItem' => $vendorDir . '/cache/cache/src/Adapter/Common/CacheItem.php', + 'Cache\\Adapter\\Common\\Exception\\CacheException' => $vendorDir . '/cache/cache/src/Adapter/Common/Exception/CacheException.php', + 'Cache\\Adapter\\Common\\Exception\\CachePoolException' => $vendorDir . '/cache/cache/src/Adapter/Common/Exception/CachePoolException.php', + 'Cache\\Adapter\\Common\\Exception\\InvalidArgumentException' => $vendorDir . '/cache/cache/src/Adapter/Common/Exception/InvalidArgumentException.php', + 'Cache\\Adapter\\Common\\HasExpirationTimestampInterface' => $vendorDir . '/cache/cache/src/Adapter/Common/HasExpirationTimestampInterface.php', + 'Cache\\Adapter\\Common\\PhpCacheItem' => $vendorDir . '/cache/cache/src/Adapter/Common/PhpCacheItem.php', + 'Cache\\Adapter\\Common\\PhpCachePool' => $vendorDir . '/cache/cache/src/Adapter/Common/PhpCachePool.php', + 'Cache\\Adapter\\Common\\TagSupportWithArray' => $vendorDir . '/cache/cache/src/Adapter/Common/TagSupportWithArray.php', + 'Cache\\Adapter\\Doctrine\\DoctrineCachePool' => $vendorDir . '/cache/cache/src/Adapter/Doctrine/DoctrineCachePool.php', + 'Cache\\Adapter\\Filesystem\\FilesystemCachePool' => $vendorDir . '/cache/cache/src/Adapter/Filesystem/FilesystemCachePool.php', + 'Cache\\Adapter\\Illuminate\\IlluminateCachePool' => $vendorDir . '/cache/cache/src/Adapter/Illuminate/IlluminateCachePool.php', + 'Cache\\Adapter\\Memcache\\MemcacheCachePool' => $vendorDir . '/cache/cache/src/Adapter/Memcache/MemcacheCachePool.php', + 'Cache\\Adapter\\Memcached\\MemcachedCachePool' => $vendorDir . '/cache/cache/src/Adapter/Memcached/MemcachedCachePool.php', + 'Cache\\Adapter\\MongoDB\\MongoDBCachePool' => $vendorDir . '/cache/cache/src/Adapter/MongoDB/MongoDBCachePool.php', + 'Cache\\Adapter\\PHPArray\\ArrayCachePool' => $vendorDir . '/cache/cache/src/Adapter/PHPArray/ArrayCachePool.php', + 'Cache\\Adapter\\Predis\\PredisCachePool' => $vendorDir . '/cache/cache/src/Adapter/Predis/PredisCachePool.php', + 'Cache\\Adapter\\Redis\\RedisCachePool' => $vendorDir . '/cache/cache/src/Adapter/Redis/RedisCachePool.php', + 'Cache\\Adapter\\Void\\VoidCachePool' => $vendorDir . '/cache/cache/src/Adapter/Void/VoidCachePool.php', + 'Cache\\Bridge\\Doctrine\\DoctrineCacheBridge' => $vendorDir . '/cache/cache/src/Bridge/Doctrine/DoctrineCacheBridge.php', + 'Cache\\Bridge\\SimpleCache\\Exception\\CacheException' => $vendorDir . '/cache/cache/src/Bridge/SimpleCache/Exception/CacheException.php', + 'Cache\\Bridge\\SimpleCache\\Exception\\InvalidArgumentException' => $vendorDir . '/cache/cache/src/Bridge/SimpleCache/Exception/InvalidArgumentException.php', + 'Cache\\Bridge\\SimpleCache\\SimpleCacheBridge' => $vendorDir . '/cache/cache/src/Bridge/SimpleCache/SimpleCacheBridge.php', + 'Cache\\Encryption\\EncryptedCachePool' => $vendorDir . '/cache/cache/src/Encryption/EncryptedCachePool.php', + 'Cache\\Encryption\\EncryptedItemDecorator' => $vendorDir . '/cache/cache/src/Encryption/EncryptedItemDecorator.php', + 'Cache\\Hierarchy\\HierarchicalCachePoolTrait' => $vendorDir . '/cache/cache/src/Hierarchy/HierarchicalCachePoolTrait.php', + 'Cache\\Hierarchy\\HierarchicalPoolInterface' => $vendorDir . '/cache/cache/src/Hierarchy/HierarchicalPoolInterface.php', + 'Cache\\Namespaced\\NamespacedCachePool' => $vendorDir . '/cache/cache/src/Namespaced/NamespacedCachePool.php', + 'Cache\\Prefixed\\PrefixedCachePool' => $vendorDir . '/cache/cache/src/Prefixed/PrefixedCachePool.php', + 'Cache\\SessionHandler\\Psr6SessionHandler' => $vendorDir . '/cache/cache/src/SessionHandler/Psr6SessionHandler.php', + 'Cache\\TagInterop\\TaggableCacheItemInterface' => $vendorDir . '/cache/cache/src/TagInterop/TaggableCacheItemInterface.php', + 'Cache\\TagInterop\\TaggableCacheItemPoolInterface' => $vendorDir . '/cache/cache/src/TagInterop/TaggableCacheItemPoolInterface.php', + 'Cache\\Taggable\\Exception\\InvalidArgumentException' => $vendorDir . '/cache/cache/src/Taggable/Exception/InvalidArgumentException.php', + 'Cache\\Taggable\\TaggablePSR6ItemAdapter' => $vendorDir . '/cache/cache/src/Taggable/TaggablePSR6ItemAdapter.php', + 'Cache\\Taggable\\TaggablePSR6PoolAdapter' => $vendorDir . '/cache/cache/src/Taggable/TaggablePSR6PoolAdapter.php', + 'DeepCopy\\DeepCopy' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/DeepCopy.php', + 'DeepCopy\\Exception\\CloneException' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php', + 'DeepCopy\\Exception\\PropertyException' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Exception/PropertyException.php', + 'DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php', + 'DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php', + 'DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php', + 'DeepCopy\\Filter\\Filter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php', + 'DeepCopy\\Filter\\KeepFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php', + 'DeepCopy\\Filter\\ReplaceFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php', + 'DeepCopy\\Filter\\SetNullFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php', + 'DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php', + 'DeepCopy\\Matcher\\Matcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.php', + 'DeepCopy\\Matcher\\PropertyMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.php', + 'DeepCopy\\Matcher\\PropertyNameMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php', + 'DeepCopy\\Matcher\\PropertyTypeMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php', + 'DeepCopy\\Reflection\\ReflectionHelper' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php', + 'DeepCopy\\TypeFilter\\Date\\DateIntervalFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php', + 'DeepCopy\\TypeFilter\\ReplaceFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php', + 'DeepCopy\\TypeFilter\\ShallowCopyFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php', + 'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedList' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php', + 'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php', + 'DeepCopy\\TypeFilter\\TypeFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php', + 'DeepCopy\\TypeMatcher\\TypeMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.php', + 'Directus\\Api\\Routes\\Activity' => $baseDir . '/src/endpoints/Activity.php', + 'Directus\\Api\\Routes\\Auth' => $baseDir . '/src/endpoints/Auth.php', + 'Directus\\Api\\Routes\\CollectionPresets' => $baseDir . '/src/endpoints/CollectionPresets.php', + 'Directus\\Api\\Routes\\Collections' => $baseDir . '/src/endpoints/Collections.php', + 'Directus\\Api\\Routes\\Fields' => $baseDir . '/src/endpoints/Fields.php', + 'Directus\\Api\\Routes\\Files' => $baseDir . '/src/endpoints/Files.php', + 'Directus\\Api\\Routes\\Home' => $baseDir . '/src/endpoints/Home.php', + 'Directus\\Api\\Routes\\Interfaces' => $baseDir . '/src/endpoints/Interfaces.php', + 'Directus\\Api\\Routes\\Items' => $baseDir . '/src/endpoints/Items.php', + 'Directus\\Api\\Routes\\Listings' => $baseDir . '/src/endpoints/Listings.php', + 'Directus\\Api\\Routes\\Pages' => $baseDir . '/src/endpoints/Pages.php', + 'Directus\\Api\\Routes\\Permissions' => $baseDir . '/src/endpoints/Permissions.php', + 'Directus\\Api\\Routes\\Relations' => $baseDir . '/src/endpoints/Relations.php', + 'Directus\\Api\\Routes\\Revisions' => $baseDir . '/src/endpoints/Revisions.php', + 'Directus\\Api\\Routes\\Roles' => $baseDir . '/src/endpoints/Roles.php', + 'Directus\\Api\\Routes\\ScimTwo' => $baseDir . '/src/endpoints/ScimTwo.php', + 'Directus\\Api\\Routes\\Server' => $baseDir . '/src/endpoints/Server.php', + 'Directus\\Api\\Routes\\Settings' => $baseDir . '/src/endpoints/Settings.php', + 'Directus\\Api\\Routes\\Types' => $baseDir . '/src/endpoints/Types.php', + 'Directus\\Api\\Routes\\Users' => $baseDir . '/src/endpoints/Users.php', + 'Directus\\Api\\Routes\\Utils' => $baseDir . '/src/endpoints/Utils.php', + 'Directus\\Application\\Application' => $baseDir . '/src/core/Directus/Application/Application.php', + 'Directus\\Application\\Container' => $baseDir . '/src/core/Directus/Application/Container.php', + 'Directus\\Application\\CoreServicesProvider' => $baseDir . '/src/core/Directus/Application/CoreServicesProvider.php', + 'Directus\\Application\\DefaultServicesProvider' => $baseDir . '/src/core/Directus/Application/DefaultServicesProvider.php', + 'Directus\\Application\\ErrorHandlers\\ErrorHandler' => $baseDir . '/src/core/Directus/Application/ErrorHandlers/ErrorHandler.php', + 'Directus\\Application\\ErrorHandlers\\MethodNotAllowedHandler' => $baseDir . '/src/core/Directus/Application/ErrorHandlers/MethodNotAllowedHandler.php', + 'Directus\\Application\\ErrorHandlers\\NotFoundHandler' => $baseDir . '/src/core/Directus/Application/ErrorHandlers/NotFoundHandler.php', + 'Directus\\Application\\Http\\Middleware\\AbstractMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/AbstractMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AbstractRateLimitMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/AbstractRateLimitMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AdminMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/AdminMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AuthenticatedMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/AuthenticatedMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AuthenticationMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/AuthenticationMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\CorsMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/CorsMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\IpRateLimitMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/IpRateLimitMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\RateLimit\\UserIdentityResolver' => $baseDir . '/src/core/Directus/Application/Http/Middleware/RateLimit/UserIdentityResolver.php', + 'Directus\\Application\\Http\\Middleware\\ResponseCacheMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/ResponseCacheMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\TableGatewayMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/TableGatewayMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\UserRateLimitMiddleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/UserRateLimitMiddleware.php', + 'Directus\\Application\\Http\\Request' => $baseDir . '/src/core/Directus/Application/Http/Request.php', + 'Directus\\Application\\Http\\Response' => $baseDir . '/src/core/Directus/Application/Http/Response.php', + 'Directus\\Application\\Route' => $baseDir . '/src/core/Directus/Application/Route.php', + 'Directus\\Authentication\\Exception\\ExpiredRequestTokenException' => $baseDir . '/src/core/Directus/Authentication/Exception/ExpiredRequestTokenException.php', + 'Directus\\Authentication\\Exception\\ExpiredResetPasswordToken' => $baseDir . '/src/core/Directus/Authentication/Exception/ExpiredResetPasswordToken.php', + 'Directus\\Authentication\\Exception\\ExpiredTokenException' => $baseDir . '/src/core/Directus/Authentication/Exception/ExpiredTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidInvitationCodeException' => $baseDir . '/src/core/Directus/Authentication/Exception/InvalidInvitationCodeException.php', + 'Directus\\Authentication\\Exception\\InvalidRequestTokenException' => $baseDir . '/src/core/Directus/Authentication/Exception/InvalidRequestTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidResetPasswordTokenException' => $baseDir . '/src/core/Directus/Authentication/Exception/InvalidResetPasswordTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidTokenException' => $baseDir . '/src/core/Directus/Authentication/Exception/InvalidTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidUserCredentialsException' => $baseDir . '/src/core/Directus/Authentication/Exception/InvalidUserCredentialsException.php', + 'Directus\\Authentication\\Exception\\UnknownUserAttributeException' => $baseDir . '/src/core/Directus/Authentication/Exception/UnknownUserAttributeException.php', + 'Directus\\Authentication\\Exception\\UserAlreadyLoggedInException' => $baseDir . '/src/core/Directus/Authentication/Exception/UserAlreadyLoggedInException.php', + 'Directus\\Authentication\\Exception\\UserInactiveException' => $baseDir . '/src/core/Directus/Authentication/Exception/UserInactiveException.php', + 'Directus\\Authentication\\Exception\\UserNotAuthenticatedException' => $baseDir . '/src/core/Directus/Authentication/Exception/UserNotAuthenticatedException.php', + 'Directus\\Authentication\\Exception\\UserNotFoundException' => $baseDir . '/src/core/Directus/Authentication/Exception/UserNotFoundException.php', + 'Directus\\Authentication\\Exception\\UserWithEmailNotFoundException' => $baseDir . '/src/core/Directus/Authentication/Exception/UserWithEmailNotFoundException.php', + 'Directus\\Authentication\\Provider' => $baseDir . '/src/core/Directus/Authentication/Provider.php', + 'Directus\\Authentication\\Sso\\AbstractSocialProvider' => $baseDir . '/src/core/Directus/Authentication/Sso/AbstractSocialProvider.php', + 'Directus\\Authentication\\Sso\\OneSocialProvider' => $baseDir . '/src/core/Directus/Authentication/Sso/OneSocialProvider.php', + 'Directus\\Authentication\\Sso\\Provider\\facebook\\Provider' => $baseDir . '/public/extensions/core/auth/facebook/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\github\\Provider' => $baseDir . '/public/extensions/core/auth/github/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\google\\Provider' => $baseDir . '/public/extensions/core/auth/google/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\okta\\Provider' => $baseDir . '/public/extensions/core/auth/okta/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\twitter\\Provider' => $baseDir . '/public/extensions/core/auth/twitter/Provider.php', + 'Directus\\Authentication\\Sso\\Social' => $baseDir . '/src/core/Directus/Authentication/Sso/Social.php', + 'Directus\\Authentication\\Sso\\SocialProviderInterface' => $baseDir . '/src/core/Directus/Authentication/Sso/SocialProviderInterface.php', + 'Directus\\Authentication\\Sso\\SocialUser' => $baseDir . '/src/core/Directus/Authentication/Sso/SocialUser.php', + 'Directus\\Authentication\\Sso\\TwoSocialProvider' => $baseDir . '/src/core/Directus/Authentication/Sso/TwoSocialProvider.php', + 'Directus\\Authentication\\User\\Provider\\UserProviderInterface' => $baseDir . '/src/core/Directus/Authentication/User/Provider/UserProviderInterface.php', + 'Directus\\Authentication\\User\\Provider\\UserTableGatewayProvider' => $baseDir . '/src/core/Directus/Authentication/User/Provider/UserTableGatewayProvider.php', + 'Directus\\Authentication\\User\\User' => $baseDir . '/src/core/Directus/Authentication/User/User.php', + 'Directus\\Authentication\\User\\UserInterface' => $baseDir . '/src/core/Directus/Authentication/User/UserInterface.php', + 'Directus\\Cache\\Cache' => $baseDir . '/src/core/Directus/Cache/Cache.php', + 'Directus\\Cache\\Response' => $baseDir . '/src/core/Directus/Cache/Response.php', + 'Directus\\Collection\\Arrayable' => $baseDir . '/src/core/Directus/Collection/Arrayable.php', + 'Directus\\Collection\\Collection' => $baseDir . '/src/core/Directus/Collection/Collection.php', + 'Directus\\Collection\\CollectionInterface' => $baseDir . '/src/core/Directus/Collection/CollectionInterface.php', + 'Directus\\Config\\Config' => $baseDir . '/src/core/Directus/Config/Config.php', + 'Directus\\Config\\ConfigInterface' => $baseDir . '/src/core/Directus/Config/ConfigInterface.php', + 'Directus\\Config\\Exception\\InvalidStatusException' => $baseDir . '/src/core/Directus/Config/Exception/InvalidStatusException.php', + 'Directus\\Config\\Exception\\InvalidValueException' => $baseDir . '/src/core/Directus/Config/Exception/InvalidValueException.php', + 'Directus\\Config\\StatusItem' => $baseDir . '/src/core/Directus/Config/StatusItem.php', + 'Directus\\Config\\StatusMapping' => $baseDir . '/src/core/Directus/Config/StatusMapping.php', + 'Directus\\Console\\Cli' => $baseDir . '/src/core/Directus/Console/Cli.php', + 'Directus\\Console\\Common\\Exception\\PasswordChangeException' => $baseDir . '/src/core/Directus/Console/Common/Exception/PasswordChangeException.php', + 'Directus\\Console\\Common\\Exception\\SettingUpdateException' => $baseDir . '/src/core/Directus/Console/Common/Exception/SettingUpdateException.php', + 'Directus\\Console\\Common\\Exception\\UserUpdateException' => $baseDir . '/src/core/Directus/Console/Common/Exception/UserUpdateException.php', + 'Directus\\Console\\Common\\Setting' => $baseDir . '/src/core/Directus/Console/Common/Setting.php', + 'Directus\\Console\\Common\\User' => $baseDir . '/src/core/Directus/Console/Common/User.php', + 'Directus\\Console\\Exception\\CommandFailedException' => $baseDir . '/src/core/Directus/Console/Exception/CommandFailedException.php', + 'Directus\\Console\\Exception\\UnsupportedCommandException' => $baseDir . '/src/core/Directus/Console/Exception/UnsupportedCommandException.php', + 'Directus\\Console\\Exception\\WrongArgumentsException' => $baseDir . '/src/core/Directus/Console/Exception/WrongArgumentsException.php', + 'Directus\\Console\\Modules\\CacheModule' => $baseDir . '/src/core/Directus/Console/Modules/CacheModule.php', + 'Directus\\Console\\Modules\\DatabaseModule' => $baseDir . '/src/core/Directus/Console/Modules/DatabaseModule.php', + 'Directus\\Console\\Modules\\InstallModule' => $baseDir . '/src/core/Directus/Console/Modules/InstallModule.php', + 'Directus\\Console\\Modules\\ModuleBase' => $baseDir . '/src/core/Directus/Console/Modules/ModuleBase.php', + 'Directus\\Console\\Modules\\ModuleInterface' => $baseDir . '/src/core/Directus/Console/Modules/ModuleInterface.php', + 'Directus\\Console\\Modules\\UserModule' => $baseDir . '/src/core/Directus/Console/Modules/UserModule.php', + 'Directus\\Container\\Container' => $baseDir . '/src/core/Directus/Container/Container.php', + 'Directus\\Container\\Exception\\ValueNotFoundException' => $baseDir . '/src/core/Directus/Container/Exception/ValueNotFoundException.php', + 'Directus\\Custom\\Hasher\\CustomHasher' => $baseDir . '/public/extensions/custom/hashers/_CustomHasher.php', + 'Directus\\Custom\\Hooks\\Products\\BeforeInsertProducts' => $baseDir . '/public/extensions/custom/hooks/_products/BeforeInsertProducts.php', + 'Directus\\Database\\Connection' => $baseDir . '/src/core/Directus/Database/Connection.php', + 'Directus\\Database\\Ddl\\Column\\Bit' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Bit.php', + 'Directus\\Database\\Ddl\\Column\\Boolean' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Boolean.php', + 'Directus\\Database\\Ddl\\Column\\CollectionLength' => $baseDir . '/src/core/Directus/Database/Ddl/Column/CollectionLength.php', + 'Directus\\Database\\Ddl\\Column\\Custom' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Custom.php', + 'Directus\\Database\\Ddl\\Column\\Double' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Double.php', + 'Directus\\Database\\Ddl\\Column\\Enum' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Enum.php', + 'Directus\\Database\\Ddl\\Column\\File' => $baseDir . '/src/core/Directus/Database/Ddl/Column/File.php', + 'Directus\\Database\\Ddl\\Column\\LongBlob' => $baseDir . '/src/core/Directus/Database/Ddl/Column/LongBlob.php', + 'Directus\\Database\\Ddl\\Column\\LongText' => $baseDir . '/src/core/Directus/Database/Ddl/Column/LongText.php', + 'Directus\\Database\\Ddl\\Column\\MediumBlob' => $baseDir . '/src/core/Directus/Database/Ddl/Column/MediumBlob.php', + 'Directus\\Database\\Ddl\\Column\\MediumInteger' => $baseDir . '/src/core/Directus/Database/Ddl/Column/MediumInteger.php', + 'Directus\\Database\\Ddl\\Column\\MediumText' => $baseDir . '/src/core/Directus/Database/Ddl/Column/MediumText.php', + 'Directus\\Database\\Ddl\\Column\\Numeric' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Numeric.php', + 'Directus\\Database\\Ddl\\Column\\Real' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Real.php', + 'Directus\\Database\\Ddl\\Column\\Serial' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Serial.php', + 'Directus\\Database\\Ddl\\Column\\Set' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Set.php', + 'Directus\\Database\\Ddl\\Column\\SmallInteger' => $baseDir . '/src/core/Directus/Database/Ddl/Column/SmallInteger.php', + 'Directus\\Database\\Ddl\\Column\\TinyBlob' => $baseDir . '/src/core/Directus/Database/Ddl/Column/TinyBlob.php', + 'Directus\\Database\\Ddl\\Column\\TinyInteger' => $baseDir . '/src/core/Directus/Database/Ddl/Column/TinyInteger.php', + 'Directus\\Database\\Ddl\\Column\\TinyText' => $baseDir . '/src/core/Directus/Database/Ddl/Column/TinyText.php', + 'Directus\\Database\\Ddl\\Column\\Uuid' => $baseDir . '/src/core/Directus/Database/Ddl/Column/Uuid.php', + 'Directus\\Database\\Exception\\CollectionAlreadyExistsException' => $baseDir . '/src/core/Directus/Database/Exception/CollectionAlreadyExistsException.php', + 'Directus\\Database\\Exception\\CollectionHasNotStatusInterfaceException' => $baseDir . '/src/core/Directus/Database/Exception/CollectionHasNotStatusInterfaceException.php', + 'Directus\\Database\\Exception\\CollectionNotFoundException' => $baseDir . '/src/core/Directus/Database/Exception/CollectionNotFoundException.php', + 'Directus\\Database\\Exception\\CollectionNotManagedException' => $baseDir . '/src/core/Directus/Database/Exception/CollectionNotManagedException.php', + 'Directus\\Database\\Exception\\ConnectionFailedException' => $baseDir . '/src/core/Directus/Database/Exception/ConnectionFailedException.php', + 'Directus\\Database\\Exception\\CustomUiValidationError' => $baseDir . '/src/core/Directus/Database/Exception/CustomUiValidationError.php', + 'Directus\\Database\\Exception\\DbException' => $baseDir . '/src/core/Directus/Database/Exception/DbException.php', + 'Directus\\Database\\Exception\\DuplicateItemException' => $baseDir . '/src/core/Directus/Database/Exception/DuplicateItemException.php', + 'Directus\\Database\\Exception\\FieldAlreadyExistsException' => $baseDir . '/src/core/Directus/Database/Exception/FieldAlreadyExistsException.php', + 'Directus\\Database\\Exception\\FieldAlreadyHasUniqueKeyException' => $baseDir . '/src/core/Directus/Database/Exception/FieldAlreadyHasUniqueKeyException.php', + 'Directus\\Database\\Exception\\FieldNotFoundException' => $baseDir . '/src/core/Directus/Database/Exception/FieldNotFoundException.php', + 'Directus\\Database\\Exception\\FieldNotManagedException' => $baseDir . '/src/core/Directus/Database/Exception/FieldNotManagedException.php', + 'Directus\\Database\\Exception\\ForbiddenSystemTableDirectAccessException' => $baseDir . '/src/core/Directus/Database/Exception/ForbiddenSystemTableDirectAccessException.php', + 'Directus\\Database\\Exception\\InvalidFieldException' => $baseDir . '/src/core/Directus/Database/Exception/InvalidFieldException.php', + 'Directus\\Database\\Exception\\InvalidQueryException' => $baseDir . '/src/core/Directus/Database/Exception/InvalidQueryException.php', + 'Directus\\Database\\Exception\\ItemNotFoundException' => $baseDir . '/src/core/Directus/Database/Exception/ItemNotFoundException.php', + 'Directus\\Database\\Exception\\RelationshipMetadataException' => $baseDir . '/src/core/Directus/Database/Exception/RelationshipMetadataException.php', + 'Directus\\Database\\Exception\\RevisionInvalidDeltaException' => $baseDir . '/src/core/Directus/Database/Exception/RevisionInvalidDeltaException.php', + 'Directus\\Database\\Exception\\RevisionNotFoundException' => $baseDir . '/src/core/Directus/Database/Exception/RevisionNotFoundException.php', + 'Directus\\Database\\Exception\\StatusMappingEmptyException' => $baseDir . '/src/core/Directus/Database/Exception/StatusMappingEmptyException.php', + 'Directus\\Database\\Exception\\StatusMappingWrongValueTypeException' => $baseDir . '/src/core/Directus/Database/Exception/StatusMappingWrongValueTypeException.php', + 'Directus\\Database\\Exception\\SuppliedArrayAsColumnValue' => $baseDir . '/src/core/Directus/Database/Exception/SuppliedArrayAsColumnValue.php', + 'Directus\\Database\\Exception\\UnknownDataTypeException' => $baseDir . '/src/core/Directus/Database/Exception/UnknownDataTypeException.php', + 'Directus\\Database\\Filters\\Filter' => $baseDir . '/src/core/Directus/Database/Filters/Filter.php', + 'Directus\\Database\\Filters\\In' => $baseDir . '/src/core/Directus/Database/Filters/In.php', + 'Directus\\Database\\Query\\Builder' => $baseDir . '/src/core/Directus/Database/Query/Builder.php', + 'Directus\\Database\\Query\\Relations\\ManyToManyRelation' => $baseDir . '/src/core/Directus/Database/Query/Relations/ManyToManyRelation.php', + 'Directus\\Database\\Query\\Relations\\ManyToOneRelation' => $baseDir . '/src/core/Directus/Database/Query/Relations/ManyToOneRelation.php', + 'Directus\\Database\\Query\\Relations\\OneToManyRelation' => $baseDir . '/src/core/Directus/Database/Query/Relations/OneToManyRelation.php', + 'Directus\\Database\\Repositories\\AbstractRepository' => $baseDir . '/src/core/Directus/Database/Repositories/AbstractRepository.php', + 'Directus\\Database\\Repositories\\Repository' => $baseDir . '/src/core/Directus/Database/Repositories/Repository.php', + 'Directus\\Database\\Repositories\\RepositoryFactory' => $baseDir . '/src/core/Directus/Database/Repositories/RepositoryFactory.php', + 'Directus\\Database\\Repositories\\RepositoryInterface' => $baseDir . '/src/core/Directus/Database/Repositories/RepositoryInterface.php', + 'Directus\\Database\\ResultItem' => $baseDir . '/src/core/Directus/Database/ResultItem.php', + 'Directus\\Database\\ResultSet' => $baseDir . '/src/core/Directus/Database/ResultSet.php', + 'Directus\\Database\\RowGateway\\BaseRowGateway' => $baseDir . '/src/core/Directus/Database/RowGateway/BaseRowGateway.php', + 'Directus\\Database\\RowGateway\\DirectusFilesRowGateway' => $baseDir . '/src/core/Directus/Database/RowGateway/DirectusMediaRowGateway.php', + 'Directus\\Database\\RowGateway\\DirectusUsersRowGateway' => $baseDir . '/src/core/Directus/Database/RowGateway/DirectusUsersRowGateway.php', + 'Directus\\Database\\SchemaService' => $baseDir . '/src/core/Directus/Database/SchemaService.php', + 'Directus\\Database\\Schema\\DataTypes' => $baseDir . '/src/core/Directus/Database/Schema/DataTypes.php', + 'Directus\\Database\\Schema\\Object\\AbstractObject' => $baseDir . '/src/core/Directus/Database/Schema/Object/AbstractObject.php', + 'Directus\\Database\\Schema\\Object\\Collection' => $baseDir . '/src/core/Directus/Database/Schema/Object/Collection.php', + 'Directus\\Database\\Schema\\Object\\Field' => $baseDir . '/src/core/Directus/Database/Schema/Object/Field.php', + 'Directus\\Database\\Schema\\Object\\FieldRelationship' => $baseDir . '/src/core/Directus/Database/Schema/Object/FieldRelationship.php', + 'Directus\\Database\\Schema\\SchemaFactory' => $baseDir . '/src/core/Directus/Database/Schema/SchemaFactory.php', + 'Directus\\Database\\Schema\\SchemaManager' => $baseDir . '/src/core/Directus/Database/Schema/SchemaManager.php', + 'Directus\\Database\\Schema\\Sources\\AbstractSchema' => $baseDir . '/src/core/Directus/Database/Schema/Sources/AbstractSchema.php', + 'Directus\\Database\\Schema\\Sources\\MySQLSchema' => $baseDir . '/src/core/Directus/Database/Schema/Sources/MySQLSchema.php', + 'Directus\\Database\\Schema\\Sources\\SQLiteSchema' => $baseDir . '/src/core/Directus/Database/Schema/Sources/SQLiteSchema.php', + 'Directus\\Database\\Schema\\Sources\\SchemaInterface' => $baseDir . '/src/core/Directus/Database/Schema/Sources/SchemaInterface.php', + 'Directus\\Database\\TableGatewayFactory' => $baseDir . '/src/core/Directus/Database/TableGatewayFactory.php', + 'Directus\\Database\\TableGateway\\BaseTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/BaseTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusActivityTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusActivityTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusCollectionPresetsTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusCollectionPresetsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusCollectionsTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusCollectionsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusPermissionsTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusPermissionsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusRolesTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusRolesTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusSettingsTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusSettingsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusUsersTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/DirectusUsersTableGateway.php', + 'Directus\\Database\\TableGateway\\RelationalTableGateway' => $baseDir . '/src/core/Directus/Database/TableGateway/RelationalTableGateway.php', + 'Directus\\Embed\\EmbedManager' => $baseDir . '/src/core/Directus/Embed/EmbedManager.php', + 'Directus\\Embed\\Provider\\AbstractProvider' => $baseDir . '/src/core/Directus/Embed/Provider/AbstractProvider.php', + 'Directus\\Embed\\Provider\\ProviderInterface' => $baseDir . '/src/core/Directus/Embed/Provider/ProviderInterface.php', + 'Directus\\Embed\\Provider\\VimeoProvider' => $baseDir . '/src/core/Directus/Embed/Provider/VimeoProvider.php', + 'Directus\\Embed\\Provider\\YoutubeProvider' => $baseDir . '/src/core/Directus/Embed/Provider/YoutubeProvider.php', + 'Directus\\Exception\\BadRequestException' => $baseDir . '/src/core/Directus/Exception/BadRequestException.php', + 'Directus\\Exception\\BadRequestExceptionInterface' => $baseDir . '/src/core/Directus/Exception/BadRequestExceptionInterface.php', + 'Directus\\Exception\\ConflictExceptionInterface' => $baseDir . '/src/core/Directus/Exception/ConflictExceptionInterface.php', + 'Directus\\Exception\\ErrorException' => $baseDir . '/src/core/Directus/Exception/ErrorException.php', + 'Directus\\Exception\\ErrorExceptionInterface' => $baseDir . '/src/core/Directus/Exception/ErrorExceptionInterface.php', + 'Directus\\Exception\\Exception' => $baseDir . '/src/core/Directus/Exception/Exception.php', + 'Directus\\Exception\\ForbiddenException' => $baseDir . '/src/core/Directus/Exception/ForbiddenException.php', + 'Directus\\Exception\\ForbiddenExceptionInterface' => $baseDir . '/src/core/Directus/Exception/ForbiddenExceptionInterface.php', + 'Directus\\Exception\\Http\\Auth\\ForbiddenException' => $baseDir . '/src/core/Directus/Exception/Http/Auth/ForbiddenException.php', + 'Directus\\Exception\\Http\\BadRequestException' => $baseDir . '/src/core/Directus/Exception/Http/BadRequestException.php', + 'Directus\\Exception\\Http\\NotFoundException' => $baseDir . '/src/core/Directus/Exception/Http/NotFoundException.php', + 'Directus\\Exception\\MethodNotAllowedException' => $baseDir . '/src/core/Directus/Exception/MethodNotAllowedException.php', + 'Directus\\Exception\\NotFoundException' => $baseDir . '/src/core/Directus/Exception/NotFoundException.php', + 'Directus\\Exception\\NotFoundExceptionInterface' => $baseDir . '/src/core/Directus/Exception/NotFoundExceptionInterface.php', + 'Directus\\Exception\\RuntimeException' => $baseDir . '/src/core/Directus/Exception/RuntimeException.php', + 'Directus\\Exception\\UnauthorizedException' => $baseDir . '/src/core/Directus/Exception/UnauthorizedException.php', + 'Directus\\Exception\\UnauthorizedExceptionInterface' => $baseDir . '/src/core/Directus/Exception/UnauthorizedExceptionInterface.php', + 'Directus\\Exception\\UnprocessableEntityExceptionInterface' => $baseDir . '/src/core/Directus/Exception/UnprocessableEntityExceptionInterface.php', + 'Directus\\Filesystem\\Exception\\FailedUploadException' => $baseDir . '/src/core/Directus/Filesystem/Exception/FailedUploadException.php', + 'Directus\\Filesystem\\Exception\\FilesystemException' => $baseDir . '/src/core/Directus/Filesystem/Exception/FilesystemException.php', + 'Directus\\Filesystem\\Exception\\ForbiddenException' => $baseDir . '/src/core/Directus/Filesystem/Exception/ForbiddenException.php', + 'Directus\\Filesystem\\Files' => $baseDir . '/src/core/Directus/Filesystem/Files.php', + 'Directus\\Filesystem\\Filesystem' => $baseDir . '/src/core/Directus/Filesystem/Filesystem.php', + 'Directus\\Filesystem\\FilesystemFactory' => $baseDir . '/src/core/Directus/Filesystem/FilesystemFactory.php', + 'Directus\\Filesystem\\Thumbnail' => $baseDir . '/src/core/Directus/Filesystem/Thumbnail.php', + 'Directus\\Filesystem\\Thumbnailer' => $baseDir . '/src/core/Directus/Filesystem/Thumbnailer.php', + 'Directus\\Hash\\Exception\\HasherNotFoundException' => $baseDir . '/src/core/Directus/Hash/Exception/HasherNotFoundException.php', + 'Directus\\Hash\\HashManager' => $baseDir . '/src/core/Directus/Hash/HashManager.php', + 'Directus\\Hash\\Hasher\\AbstractHashHasher' => $baseDir . '/src/core/Directus/Hash/Hasher/AbstractHashHasher.php', + 'Directus\\Hash\\Hasher\\BCryptHasher' => $baseDir . '/src/core/Directus/Hash/Hasher/BCryptHasher.php', + 'Directus\\Hash\\Hasher\\CoreHasher' => $baseDir . '/src/core/Directus/Hash/Hasher/CoreHasher.php', + 'Directus\\Hash\\Hasher\\HasherInterface' => $baseDir . '/src/core/Directus/Hash/Hasher/HasherInterface.php', + 'Directus\\Hash\\Hasher\\MD5Hasher' => $baseDir . '/src/core/Directus/Hash/Hasher/MD5Hasher.php', + 'Directus\\Hash\\Hasher\\Sha1Hasher' => $baseDir . '/src/core/Directus/Hash/Hasher/Sha1Hasher.php', + 'Directus\\Hash\\Hasher\\Sha224Hasher' => $baseDir . '/src/core/Directus/Hash/Hasher/Sha224Hasher.php', + 'Directus\\Hash\\Hasher\\Sha256Hasher' => $baseDir . '/src/core/Directus/Hash/Hasher/Sha256Hasher.php', + 'Directus\\Hash\\Hasher\\Sha384Hasher' => $baseDir . '/src/core/Directus/Hash/Hasher/Sha384Hasher.php', + 'Directus\\Hash\\Hasher\\Sha512Hasher' => $baseDir . '/src/core/Directus/Hash/Hasher/Sha512Hasher.php', + 'Directus\\Hook\\Emitter' => $baseDir . '/src/core/Directus/Hook/Emitter.php', + 'Directus\\Hook\\HookInterface' => $baseDir . '/src/core/Directus/Hook/HookInterface.php', + 'Directus\\Hook\\Payload' => $baseDir . '/src/core/Directus/Hook/Payload.php', + 'Directus\\Mail\\Exception\\InvalidTransportException' => $baseDir . '/src/core/Directus/Mail/Exception/InvalidTransportException.php', + 'Directus\\Mail\\Exception\\InvalidTransportObjectException' => $baseDir . '/src/core/Directus/Mail/Exception/InvalidTransportObjectException.php', + 'Directus\\Mail\\Exception\\TransportNotFoundException' => $baseDir . '/src/core/Directus/Mail/Exception/TransportNotFoundException.php', + 'Directus\\Mail\\Mailer' => $baseDir . '/src/core/Directus/Mail/Mailer.php', + 'Directus\\Mail\\Message' => $baseDir . '/src/core/Directus/Mail/Message.php', + 'Directus\\Mail\\TransportManager' => $baseDir . '/src/core/Directus/Mail/TransportManager.php', + 'Directus\\Mail\\Transports\\AbstractTransport' => $baseDir . '/src/core/Directus/Mail/Transports/AbstractTransport.php', + 'Directus\\Mail\\Transports\\SendMailTransport' => $baseDir . '/src/core/Directus/Mail/Transports/SendMailTransport.php', + 'Directus\\Mail\\Transports\\SimpleFileTransport' => $baseDir . '/src/core/Directus/Mail/Transports/SimpleFileTransport.php', + 'Directus\\Mail\\Transports\\SmtpTransport' => $baseDir . '/src/core/Directus/Mail/Transports/SmtpTransport.php', + 'Directus\\Permissions\\Acl' => $baseDir . '/src/core/Directus/Permissions/Acl.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionAlterException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionAlterException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionCreateException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionCreateException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionDeleteException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionDeleteException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionReadException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionReadException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionUpdateException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionUpdateException.php', + 'Directus\\Permissions\\Exception\\ForbiddenException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenException.php', + 'Directus\\Permissions\\Exception\\ForbiddenFieldReadException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenFieldReadException.php', + 'Directus\\Permissions\\Exception\\ForbiddenFieldWriteException' => $baseDir . '/src/core/Directus/Permissions/Exception/ForbiddenFieldWriteException.php', + 'Directus\\Permissions\\Exception\\PermissionException' => $baseDir . '/src/core/Directus/Permissions/Exception/PermissionException.php', + 'Directus\\Services\\AbstractExtensionsController' => $baseDir . '/src/core/Directus/Services/AbstractExtensionsController.php', + 'Directus\\Services\\AbstractService' => $baseDir . '/src/core/Directus/Services/AbstractService.php', + 'Directus\\Services\\ActivityService' => $baseDir . '/src/core/Directus/Services/ActivityService.php', + 'Directus\\Services\\AuthService' => $baseDir . '/src/core/Directus/Services/AuthService.php', + 'Directus\\Services\\CollectionPresetsService' => $baseDir . '/src/core/Directus/Services/CollectionPresetsService.php', + 'Directus\\Services\\FilesServices' => $baseDir . '/src/core/Directus/Services/FilesServices.php', + 'Directus\\Services\\InterfacesService' => $baseDir . '/src/core/Directus/Services/InterfacesService.php', + 'Directus\\Services\\ItemsService' => $baseDir . '/src/core/Directus/Services/ItemsService.php', + 'Directus\\Services\\ListingsService' => $baseDir . '/src/core/Directus/Services/ListingsService.php', + 'Directus\\Services\\PagesService' => $baseDir . '/src/core/Directus/Services/PagesService.php', + 'Directus\\Services\\PermissionsService' => $baseDir . '/src/core/Directus/Services/PermissionsService.php', + 'Directus\\Services\\RelationsService' => $baseDir . '/src/core/Directus/Services/RelationsService.php', + 'Directus\\Services\\RevisionsService' => $baseDir . '/src/core/Directus/Services/RevisionsService.php', + 'Directus\\Services\\RolesService' => $baseDir . '/src/core/Directus/Services/RolesService.php', + 'Directus\\Services\\ScimService' => $baseDir . '/src/core/Directus/Services/ScimService.php', + 'Directus\\Services\\ServerService' => $baseDir . '/src/core/Directus/Services/ServerService.php', + 'Directus\\Services\\SettingsService' => $baseDir . '/src/core/Directus/Services/SettingsService.php', + 'Directus\\Services\\TablesService' => $baseDir . '/src/core/Directus/Services/TablesService.php', + 'Directus\\Services\\UsersService' => $baseDir . '/src/core/Directus/Services/UsersService.php', + 'Directus\\Services\\UtilsService' => $baseDir . '/src/core/Directus/Services/UtilsService.php', + 'Directus\\Session\\Session' => $baseDir . '/src/core/Directus/Session/Session.php', + 'Directus\\Session\\Storage\\ArraySessionStorage' => $baseDir . '/src/core/Directus/Session/Storage/ArraySessionStorage.php', + 'Directus\\Session\\Storage\\NativeSessionStorage' => $baseDir . '/src/core/Directus/Session/Storage/NativeSessionStorage.php', + 'Directus\\Session\\Storage\\SessionStorageInterface' => $baseDir . '/src/core/Directus/Session/Storage/SessionStorageInterface.php', + 'Directus\\Slim\\Middleware' => $baseDir . '/src/core/Directus/Application/Http/Middleware/Middleware.php', + 'Directus\\Util\\ArrayUtils' => $baseDir . '/src/core/Directus/Util/ArrayUtils.php', + 'Directus\\Util\\DateTimeUtils' => $baseDir . '/src/core/Directus/Util/DateTimeUtils.php', + 'Directus\\Util\\Formatting' => $baseDir . '/src/core/Directus/Util/Formatting.php', + 'Directus\\Util\\Git' => $baseDir . '/src/core/Directus/Util/Git.php', + 'Directus\\Util\\Installation\\InstallerUtils' => $baseDir . '/src/core/Directus/Util/Installation/InstallerUtils.php', + 'Directus\\Util\\JWTUtils' => $baseDir . '/src/core/Directus/Util/JWTUtils.php', + 'Directus\\Util\\SchemaUtils' => $baseDir . '/src/core/Directus/Util/SchemaUtils.php', + 'Directus\\Util\\StringUtils' => $baseDir . '/src/core/Directus/Util/StringUtils.php', + 'Directus\\Validator\\Constraints\\Required' => $baseDir . '/src/core/Directus/Validator/Constraints/Required.php', + 'Directus\\Validator\\Constraints\\RequiredValidator' => $baseDir . '/src/core/Directus/Validator/Constraints/RequiredValidator.php', + 'Directus\\Validator\\Exception\\InvalidRequestException' => $baseDir . '/src/core/Directus/Validator/Exception/InvalidRequestException.php', + 'Directus\\Validator\\Exception\\UnknownConstraintException' => $baseDir . '/src/core/Directus/Validator/Exception/UnknownConstraintException.php', + 'Directus\\Validator\\Validator' => $baseDir . '/src/core/Directus/Validator/Validator.php', + 'Doctrine\\Common\\Cache\\ApcCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php', + 'Doctrine\\Common\\Cache\\ApcuCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php', + 'Doctrine\\Common\\Cache\\ArrayCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php', + 'Doctrine\\Common\\Cache\\Cache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php', + 'Doctrine\\Common\\Cache\\CacheProvider' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php', + 'Doctrine\\Common\\Cache\\ChainCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php', + 'Doctrine\\Common\\Cache\\ClearableCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php', + 'Doctrine\\Common\\Cache\\CouchbaseCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php', + 'Doctrine\\Common\\Cache\\ExtMongoDBCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php', + 'Doctrine\\Common\\Cache\\FileCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php', + 'Doctrine\\Common\\Cache\\FilesystemCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php', + 'Doctrine\\Common\\Cache\\FlushableCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php', + 'Doctrine\\Common\\Cache\\LegacyMongoDBCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/LegacyMongoDBCache.php', + 'Doctrine\\Common\\Cache\\MemcacheCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php', + 'Doctrine\\Common\\Cache\\MemcachedCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php', + 'Doctrine\\Common\\Cache\\MongoDBCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php', + 'Doctrine\\Common\\Cache\\MultiDeleteCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php', + 'Doctrine\\Common\\Cache\\MultiGetCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php', + 'Doctrine\\Common\\Cache\\MultiOperationCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiOperationCache.php', + 'Doctrine\\Common\\Cache\\MultiPutCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php', + 'Doctrine\\Common\\Cache\\PhpFileCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php', + 'Doctrine\\Common\\Cache\\PredisCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php', + 'Doctrine\\Common\\Cache\\RedisCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php', + 'Doctrine\\Common\\Cache\\RiakCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php', + 'Doctrine\\Common\\Cache\\SQLite3Cache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php', + 'Doctrine\\Common\\Cache\\Version' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/Version.php', + 'Doctrine\\Common\\Cache\\VoidCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php', + 'Doctrine\\Common\\Cache\\WinCacheCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php', + 'Doctrine\\Common\\Cache\\XcacheCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php', + 'Doctrine\\Common\\Cache\\ZendDataCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php', + 'Doctrine\\Instantiator\\Exception\\ExceptionInterface' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php', + 'Doctrine\\Instantiator\\Exception\\InvalidArgumentException' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php', + 'Doctrine\\Instantiator\\Exception\\UnexpectedValueException' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php', + 'Doctrine\\Instantiator\\Instantiator' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php', + 'Doctrine\\Instantiator\\InstantiatorInterface' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php', + 'FastRoute\\BadRouteException' => $vendorDir . '/nikic/fast-route/src/BadRouteException.php', + 'FastRoute\\DataGenerator' => $vendorDir . '/nikic/fast-route/src/DataGenerator.php', + 'FastRoute\\DataGenerator\\CharCountBased' => $vendorDir . '/nikic/fast-route/src/DataGenerator/CharCountBased.php', + 'FastRoute\\DataGenerator\\GroupCountBased' => $vendorDir . '/nikic/fast-route/src/DataGenerator/GroupCountBased.php', + 'FastRoute\\DataGenerator\\GroupPosBased' => $vendorDir . '/nikic/fast-route/src/DataGenerator/GroupPosBased.php', + 'FastRoute\\DataGenerator\\MarkBased' => $vendorDir . '/nikic/fast-route/src/DataGenerator/MarkBased.php', + 'FastRoute\\DataGenerator\\RegexBasedAbstract' => $vendorDir . '/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php', + 'FastRoute\\Dispatcher' => $vendorDir . '/nikic/fast-route/src/Dispatcher.php', + 'FastRoute\\Dispatcher\\CharCountBased' => $vendorDir . '/nikic/fast-route/src/Dispatcher/CharCountBased.php', + 'FastRoute\\Dispatcher\\GroupCountBased' => $vendorDir . '/nikic/fast-route/src/Dispatcher/GroupCountBased.php', + 'FastRoute\\Dispatcher\\GroupPosBased' => $vendorDir . '/nikic/fast-route/src/Dispatcher/GroupPosBased.php', + 'FastRoute\\Dispatcher\\MarkBased' => $vendorDir . '/nikic/fast-route/src/Dispatcher/MarkBased.php', + 'FastRoute\\Dispatcher\\RegexBasedAbstract' => $vendorDir . '/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php', + 'FastRoute\\Route' => $vendorDir . '/nikic/fast-route/src/Route.php', + 'FastRoute\\RouteCollector' => $vendorDir . '/nikic/fast-route/src/RouteCollector.php', + 'FastRoute\\RouteParser' => $vendorDir . '/nikic/fast-route/src/RouteParser.php', + 'FastRoute\\RouteParser\\Std' => $vendorDir . '/nikic/fast-route/src/RouteParser/Std.php', + 'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php', + 'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php', + 'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', + 'Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php', + 'Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php', + 'Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php', + 'Firebase\\JWT\\SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/src/SignatureInvalidException.php', + 'GuzzleHttp\\Client' => $vendorDir . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\SeekException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/SeekException.php', + 'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => $vendorDir . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\MockHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\Middleware' => $vendorDir . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => $vendorDir . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => $vendorDir . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => $vendorDir . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\EachPromise' => $vendorDir . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => $vendorDir . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Promise' => $vendorDir . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => $vendorDir . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => $vendorDir . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => $vendorDir . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => $vendorDir . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => $vendorDir . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => $vendorDir . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\RedirectMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => $vendorDir . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => $vendorDir . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\UriTemplate' => $vendorDir . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'Interop\\Container\\ContainerInterface' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/ContainerInterface.php', + 'Interop\\Container\\Exception\\ContainerException' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php', + 'Interop\\Container\\Exception\\NotFoundException' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php', + 'Intervention\\Image\\AbstractColor' => $vendorDir . '/intervention/image/src/Intervention/Image/AbstractColor.php', + 'Intervention\\Image\\AbstractDecoder' => $vendorDir . '/intervention/image/src/Intervention/Image/AbstractDecoder.php', + 'Intervention\\Image\\AbstractDriver' => $vendorDir . '/intervention/image/src/Intervention/Image/AbstractDriver.php', + 'Intervention\\Image\\AbstractEncoder' => $vendorDir . '/intervention/image/src/Intervention/Image/AbstractEncoder.php', + 'Intervention\\Image\\AbstractFont' => $vendorDir . '/intervention/image/src/Intervention/Image/AbstractFont.php', + 'Intervention\\Image\\AbstractShape' => $vendorDir . '/intervention/image/src/Intervention/Image/AbstractShape.php', + 'Intervention\\Image\\Commands\\AbstractCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php', + 'Intervention\\Image\\Commands\\Argument' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/Argument.php', + 'Intervention\\Image\\Commands\\ChecksumCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php', + 'Intervention\\Image\\Commands\\CircleCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/CircleCommand.php', + 'Intervention\\Image\\Commands\\EllipseCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php', + 'Intervention\\Image\\Commands\\ExifCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/ExifCommand.php', + 'Intervention\\Image\\Commands\\IptcCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/IptcCommand.php', + 'Intervention\\Image\\Commands\\LineCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/LineCommand.php', + 'Intervention\\Image\\Commands\\OrientateCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php', + 'Intervention\\Image\\Commands\\PolygonCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php', + 'Intervention\\Image\\Commands\\PsrResponseCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php', + 'Intervention\\Image\\Commands\\RectangleCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php', + 'Intervention\\Image\\Commands\\ResponseCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php', + 'Intervention\\Image\\Commands\\StreamCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/StreamCommand.php', + 'Intervention\\Image\\Commands\\TextCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Commands/TextCommand.php', + 'Intervention\\Image\\Constraint' => $vendorDir . '/intervention/image/src/Intervention/Image/Constraint.php', + 'Intervention\\Image\\Exception\\ImageException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/ImageException.php', + 'Intervention\\Image\\Exception\\InvalidArgumentException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/InvalidArgumentException.php', + 'Intervention\\Image\\Exception\\MissingDependencyException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/MissingDependencyException.php', + 'Intervention\\Image\\Exception\\NotFoundException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/NotFoundException.php', + 'Intervention\\Image\\Exception\\NotReadableException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/NotReadableException.php', + 'Intervention\\Image\\Exception\\NotSupportedException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/NotSupportedException.php', + 'Intervention\\Image\\Exception\\NotWritableException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/NotWritableException.php', + 'Intervention\\Image\\Exception\\RuntimeException' => $vendorDir . '/intervention/image/src/Intervention/Image/Exception/RuntimeException.php', + 'Intervention\\Image\\Facades\\Image' => $vendorDir . '/intervention/image/src/Intervention/Image/Facades/Image.php', + 'Intervention\\Image\\File' => $vendorDir . '/intervention/image/src/Intervention/Image/File.php', + 'Intervention\\Image\\Filters\\DemoFilter' => $vendorDir . '/intervention/image/src/Intervention/Image/Filters/DemoFilter.php', + 'Intervention\\Image\\Filters\\FilterInterface' => $vendorDir . '/intervention/image/src/Intervention/Image/Filters/FilterInterface.php', + 'Intervention\\Image\\Gd\\Color' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Color.php', + 'Intervention\\Image\\Gd\\Commands\\BackupCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php', + 'Intervention\\Image\\Gd\\Commands\\BlurCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php', + 'Intervention\\Image\\Gd\\Commands\\BrightnessCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ColorizeCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ContrastCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php', + 'Intervention\\Image\\Gd\\Commands\\CropCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php', + 'Intervention\\Image\\Gd\\Commands\\DestroyCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php', + 'Intervention\\Image\\Gd\\Commands\\FillCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php', + 'Intervention\\Image\\Gd\\Commands\\FitCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php', + 'Intervention\\Image\\Gd\\Commands\\FlipCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php', + 'Intervention\\Image\\Gd\\Commands\\GammaCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php', + 'Intervention\\Image\\Gd\\Commands\\GetSizeCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php', + 'Intervention\\Image\\Gd\\Commands\\GreyscaleCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php', + 'Intervention\\Image\\Gd\\Commands\\HeightenCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php', + 'Intervention\\Image\\Gd\\Commands\\InsertCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php', + 'Intervention\\Image\\Gd\\Commands\\InterlaceCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php', + 'Intervention\\Image\\Gd\\Commands\\InvertCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php', + 'Intervention\\Image\\Gd\\Commands\\LimitColorsCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php', + 'Intervention\\Image\\Gd\\Commands\\MaskCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php', + 'Intervention\\Image\\Gd\\Commands\\OpacityCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php', + 'Intervention\\Image\\Gd\\Commands\\PickColorCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php', + 'Intervention\\Image\\Gd\\Commands\\PixelCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php', + 'Intervention\\Image\\Gd\\Commands\\PixelateCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ResetCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ResizeCanvasCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ResizeCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php', + 'Intervention\\Image\\Gd\\Commands\\RotateCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php', + 'Intervention\\Image\\Gd\\Commands\\SharpenCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php', + 'Intervention\\Image\\Gd\\Commands\\TrimCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php', + 'Intervention\\Image\\Gd\\Commands\\WidenCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php', + 'Intervention\\Image\\Gd\\Decoder' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Decoder.php', + 'Intervention\\Image\\Gd\\Driver' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Driver.php', + 'Intervention\\Image\\Gd\\Encoder' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Encoder.php', + 'Intervention\\Image\\Gd\\Font' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Font.php', + 'Intervention\\Image\\Gd\\Shapes\\CircleShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php', + 'Intervention\\Image\\Gd\\Shapes\\EllipseShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php', + 'Intervention\\Image\\Gd\\Shapes\\LineShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php', + 'Intervention\\Image\\Gd\\Shapes\\PolygonShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php', + 'Intervention\\Image\\Gd\\Shapes\\RectangleShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php', + 'Intervention\\Image\\Image' => $vendorDir . '/intervention/image/src/Intervention/Image/Image.php', + 'Intervention\\Image\\ImageManager' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageManager.php', + 'Intervention\\Image\\ImageManagerStatic' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageManagerStatic.php', + 'Intervention\\Image\\ImageServiceProvider' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageServiceProvider.php', + 'Intervention\\Image\\ImageServiceProviderLaravel4' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php', + 'Intervention\\Image\\ImageServiceProviderLaravel5' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel5.php', + 'Intervention\\Image\\ImageServiceProviderLeague' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php', + 'Intervention\\Image\\ImageServiceProviderLumen' => $vendorDir . '/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php', + 'Intervention\\Image\\Imagick\\Color' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Color.php', + 'Intervention\\Image\\Imagick\\Commands\\BackupCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\BlurCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\BrightnessCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ColorizeCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ContrastCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\CropCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\DestroyCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ExifCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\FillCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\FitCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\FlipCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\GammaCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\GetSizeCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\GreyscaleCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\HeightenCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\InsertCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\InterlaceCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\InvertCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\LimitColorsCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\MaskCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\OpacityCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\PickColorCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\PixelCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\PixelateCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ResetCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ResizeCanvasCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ResizeCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\RotateCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\SharpenCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\TrimCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\WidenCommand' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php', + 'Intervention\\Image\\Imagick\\Decoder' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Decoder.php', + 'Intervention\\Image\\Imagick\\Driver' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Driver.php', + 'Intervention\\Image\\Imagick\\Encoder' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Encoder.php', + 'Intervention\\Image\\Imagick\\Font' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Font.php', + 'Intervention\\Image\\Imagick\\Shapes\\CircleShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\EllipseShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\LineShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\PolygonShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\RectangleShape' => $vendorDir . '/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php', + 'Intervention\\Image\\Point' => $vendorDir . '/intervention/image/src/Intervention/Image/Point.php', + 'Intervention\\Image\\Response' => $vendorDir . '/intervention/image/src/Intervention/Image/Response.php', + 'Intervention\\Image\\Size' => $vendorDir . '/intervention/image/src/Intervention/Image/Size.php', + 'League\\Flysystem\\AdapterInterface' => $vendorDir . '/league/flysystem/src/AdapterInterface.php', + 'League\\Flysystem\\Adapter\\AbstractAdapter' => $vendorDir . '/league/flysystem/src/Adapter/AbstractAdapter.php', + 'League\\Flysystem\\Adapter\\AbstractFtpAdapter' => $vendorDir . '/league/flysystem/src/Adapter/AbstractFtpAdapter.php', + 'League\\Flysystem\\Adapter\\CanOverwriteFiles' => $vendorDir . '/league/flysystem/src/Adapter/CanOverwriteFiles.php', + 'League\\Flysystem\\Adapter\\Ftp' => $vendorDir . '/league/flysystem/src/Adapter/Ftp.php', + 'League\\Flysystem\\Adapter\\Ftpd' => $vendorDir . '/league/flysystem/src/Adapter/Ftpd.php', + 'League\\Flysystem\\Adapter\\Local' => $vendorDir . '/league/flysystem/src/Adapter/Local.php', + 'League\\Flysystem\\Adapter\\NullAdapter' => $vendorDir . '/league/flysystem/src/Adapter/NullAdapter.php', + 'League\\Flysystem\\Adapter\\Polyfill\\NotSupportingVisibilityTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedCopyTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedReadingTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedWritingTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php', + 'League\\Flysystem\\Adapter\\SynologyFtp' => $vendorDir . '/league/flysystem/src/Adapter/SynologyFtp.php', + 'League\\Flysystem\\Config' => $vendorDir . '/league/flysystem/src/Config.php', + 'League\\Flysystem\\ConfigAwareTrait' => $vendorDir . '/league/flysystem/src/ConfigAwareTrait.php', + 'League\\Flysystem\\Directory' => $vendorDir . '/league/flysystem/src/Directory.php', + 'League\\Flysystem\\Exception' => $vendorDir . '/league/flysystem/src/Exception.php', + 'League\\Flysystem\\File' => $vendorDir . '/league/flysystem/src/File.php', + 'League\\Flysystem\\FileExistsException' => $vendorDir . '/league/flysystem/src/FileExistsException.php', + 'League\\Flysystem\\FileNotFoundException' => $vendorDir . '/league/flysystem/src/FileNotFoundException.php', + 'League\\Flysystem\\Filesystem' => $vendorDir . '/league/flysystem/src/Filesystem.php', + 'League\\Flysystem\\FilesystemInterface' => $vendorDir . '/league/flysystem/src/FilesystemInterface.php', + 'League\\Flysystem\\FilesystemNotFoundException' => $vendorDir . '/league/flysystem/src/FilesystemNotFoundException.php', + 'League\\Flysystem\\Handler' => $vendorDir . '/league/flysystem/src/Handler.php', + 'League\\Flysystem\\MountManager' => $vendorDir . '/league/flysystem/src/MountManager.php', + 'League\\Flysystem\\NotSupportedException' => $vendorDir . '/league/flysystem/src/NotSupportedException.php', + 'League\\Flysystem\\PluginInterface' => $vendorDir . '/league/flysystem/src/PluginInterface.php', + 'League\\Flysystem\\Plugin\\AbstractPlugin' => $vendorDir . '/league/flysystem/src/Plugin/AbstractPlugin.php', + 'League\\Flysystem\\Plugin\\EmptyDir' => $vendorDir . '/league/flysystem/src/Plugin/EmptyDir.php', + 'League\\Flysystem\\Plugin\\ForcedCopy' => $vendorDir . '/league/flysystem/src/Plugin/ForcedCopy.php', + 'League\\Flysystem\\Plugin\\ForcedRename' => $vendorDir . '/league/flysystem/src/Plugin/ForcedRename.php', + 'League\\Flysystem\\Plugin\\GetWithMetadata' => $vendorDir . '/league/flysystem/src/Plugin/GetWithMetadata.php', + 'League\\Flysystem\\Plugin\\ListFiles' => $vendorDir . '/league/flysystem/src/Plugin/ListFiles.php', + 'League\\Flysystem\\Plugin\\ListPaths' => $vendorDir . '/league/flysystem/src/Plugin/ListPaths.php', + 'League\\Flysystem\\Plugin\\ListWith' => $vendorDir . '/league/flysystem/src/Plugin/ListWith.php', + 'League\\Flysystem\\Plugin\\PluggableTrait' => $vendorDir . '/league/flysystem/src/Plugin/PluggableTrait.php', + 'League\\Flysystem\\Plugin\\PluginNotFoundException' => $vendorDir . '/league/flysystem/src/Plugin/PluginNotFoundException.php', + 'League\\Flysystem\\ReadInterface' => $vendorDir . '/league/flysystem/src/ReadInterface.php', + 'League\\Flysystem\\RootViolationException' => $vendorDir . '/league/flysystem/src/RootViolationException.php', + 'League\\Flysystem\\SafeStorage' => $vendorDir . '/league/flysystem/src/SafeStorage.php', + 'League\\Flysystem\\UnreadableFileException' => $vendorDir . '/league/flysystem/src/UnreadableFileException.php', + 'League\\Flysystem\\Util' => $vendorDir . '/league/flysystem/src/Util.php', + 'League\\Flysystem\\Util\\ContentListingFormatter' => $vendorDir . '/league/flysystem/src/Util/ContentListingFormatter.php', + 'League\\Flysystem\\Util\\MimeType' => $vendorDir . '/league/flysystem/src/Util/MimeType.php', + 'League\\Flysystem\\Util\\StreamHasher' => $vendorDir . '/league/flysystem/src/Util/StreamHasher.php', + 'League\\OAuth1\\Client\\Credentials\\ClientCredentials' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/ClientCredentials.php', + 'League\\OAuth1\\Client\\Credentials\\ClientCredentialsInterface' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/ClientCredentialsInterface.php', + 'League\\OAuth1\\Client\\Credentials\\Credentials' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/Credentials.php', + 'League\\OAuth1\\Client\\Credentials\\CredentialsException' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/CredentialsException.php', + 'League\\OAuth1\\Client\\Credentials\\CredentialsInterface' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/CredentialsInterface.php', + 'League\\OAuth1\\Client\\Credentials\\TemporaryCredentials' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/TemporaryCredentials.php', + 'League\\OAuth1\\Client\\Credentials\\TokenCredentials' => $vendorDir . '/league/oauth1-client/src/Client/Credentials/TokenCredentials.php', + 'League\\OAuth1\\Client\\Server\\Bitbucket' => $vendorDir . '/league/oauth1-client/src/Client/Server/Bitbucket.php', + 'League\\OAuth1\\Client\\Server\\Magento' => $vendorDir . '/league/oauth1-client/src/Client/Server/Magento.php', + 'League\\OAuth1\\Client\\Server\\Server' => $vendorDir . '/league/oauth1-client/src/Client/Server/Server.php', + 'League\\OAuth1\\Client\\Server\\Trello' => $vendorDir . '/league/oauth1-client/src/Client/Server/Trello.php', + 'League\\OAuth1\\Client\\Server\\Tumblr' => $vendorDir . '/league/oauth1-client/src/Client/Server/Tumblr.php', + 'League\\OAuth1\\Client\\Server\\Twitter' => $vendorDir . '/league/oauth1-client/src/Client/Server/Twitter.php', + 'League\\OAuth1\\Client\\Server\\User' => $vendorDir . '/league/oauth1-client/src/Client/Server/User.php', + 'League\\OAuth1\\Client\\Server\\Uservoice' => $vendorDir . '/league/oauth1-client/src/Client/Server/Uservoice.php', + 'League\\OAuth1\\Client\\Server\\Xing' => $vendorDir . '/league/oauth1-client/src/Client/Server/Xing.php', + 'League\\OAuth1\\Client\\Signature\\HmacSha1Signature' => $vendorDir . '/league/oauth1-client/src/Client/Signature/HmacSha1Signature.php', + 'League\\OAuth1\\Client\\Signature\\PlainTextSignature' => $vendorDir . '/league/oauth1-client/src/Client/Signature/PlainTextSignature.php', + 'League\\OAuth1\\Client\\Signature\\Signature' => $vendorDir . '/league/oauth1-client/src/Client/Signature/Signature.php', + 'League\\OAuth1\\Client\\Signature\\SignatureInterface' => $vendorDir . '/league/oauth1-client/src/Client/Signature/SignatureInterface.php', + 'League\\OAuth2\\Client\\Exception\\HostedDomainException' => $vendorDir . '/league/oauth2-google/src/Exception/HostedDomainException.php', + 'League\\OAuth2\\Client\\Grant\\AbstractGrant' => $vendorDir . '/league/oauth2-client/src/Grant/AbstractGrant.php', + 'League\\OAuth2\\Client\\Grant\\AuthorizationCode' => $vendorDir . '/league/oauth2-client/src/Grant/AuthorizationCode.php', + 'League\\OAuth2\\Client\\Grant\\ClientCredentials' => $vendorDir . '/league/oauth2-client/src/Grant/ClientCredentials.php', + 'League\\OAuth2\\Client\\Grant\\Exception\\InvalidGrantException' => $vendorDir . '/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php', + 'League\\OAuth2\\Client\\Grant\\FbExchangeToken' => $vendorDir . '/league/oauth2-facebook/src/Grant/FbExchangeToken.php', + 'League\\OAuth2\\Client\\Grant\\GrantFactory' => $vendorDir . '/league/oauth2-client/src/Grant/GrantFactory.php', + 'League\\OAuth2\\Client\\Grant\\Password' => $vendorDir . '/league/oauth2-client/src/Grant/Password.php', + 'League\\OAuth2\\Client\\Grant\\RefreshToken' => $vendorDir . '/league/oauth2-client/src/Grant/RefreshToken.php', + 'League\\OAuth2\\Client\\Provider\\AbstractProvider' => $vendorDir . '/league/oauth2-client/src/Provider/AbstractProvider.php', + 'League\\OAuth2\\Client\\Provider\\AppSecretProof' => $vendorDir . '/league/oauth2-facebook/src/Provider/AppSecretProof.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\FacebookProviderException' => $vendorDir . '/league/oauth2-facebook/src/Provider/Exception/FacebookProviderException.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\GithubIdentityProviderException' => $vendorDir . '/league/oauth2-github/src/Provider/Exception/GithubIdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException' => $vendorDir . '/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\Facebook' => $vendorDir . '/league/oauth2-facebook/src/Provider/Facebook.php', + 'League\\OAuth2\\Client\\Provider\\FacebookUser' => $vendorDir . '/league/oauth2-facebook/src/Provider/FacebookUser.php', + 'League\\OAuth2\\Client\\Provider\\GenericProvider' => $vendorDir . '/league/oauth2-client/src/Provider/GenericProvider.php', + 'League\\OAuth2\\Client\\Provider\\GenericResourceOwner' => $vendorDir . '/league/oauth2-client/src/Provider/GenericResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Github' => $vendorDir . '/league/oauth2-github/src/Provider/Github.php', + 'League\\OAuth2\\Client\\Provider\\GithubResourceOwner' => $vendorDir . '/league/oauth2-github/src/Provider/GithubResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Google' => $vendorDir . '/league/oauth2-google/src/Provider/Google.php', + 'League\\OAuth2\\Client\\Provider\\GoogleUser' => $vendorDir . '/league/oauth2-google/src/Provider/GoogleUser.php', + 'League\\OAuth2\\Client\\Provider\\ResourceOwnerInterface' => $vendorDir . '/league/oauth2-client/src/Provider/ResourceOwnerInterface.php', + 'League\\OAuth2\\Client\\Token\\AccessToken' => $vendorDir . '/league/oauth2-client/src/Token/AccessToken.php', + 'League\\OAuth2\\Client\\Tool\\ArrayAccessorTrait' => $vendorDir . '/league/oauth2-client/src/Tool/ArrayAccessorTrait.php', + 'League\\OAuth2\\Client\\Tool\\BearerAuthorizationTrait' => $vendorDir . '/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\MacAuthorizationTrait' => $vendorDir . '/league/oauth2-client/src/Tool/MacAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\ProviderRedirectTrait' => $vendorDir . '/league/oauth2-client/src/Tool/ProviderRedirectTrait.php', + 'League\\OAuth2\\Client\\Tool\\QueryBuilderTrait' => $vendorDir . '/league/oauth2-client/src/Tool/QueryBuilderTrait.php', + 'League\\OAuth2\\Client\\Tool\\RequestFactory' => $vendorDir . '/league/oauth2-client/src/Tool/RequestFactory.php', + 'League\\OAuth2\\Client\\Tool\\RequiredParameterTrait' => $vendorDir . '/league/oauth2-client/src/Tool/RequiredParameterTrait.php', + 'Monolog\\ErrorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/ErrorHandler.php', + 'Monolog\\Formatter\\ChromePHPFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', + 'Monolog\\Formatter\\ElasticaFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php', + 'Monolog\\Formatter\\FlowdockFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php', + 'Monolog\\Formatter\\FluentdFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php', + 'Monolog\\Formatter\\FormatterInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', + 'Monolog\\Formatter\\GelfMessageFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', + 'Monolog\\Formatter\\HtmlFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php', + 'Monolog\\Formatter\\JsonFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', + 'Monolog\\Formatter\\LineFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', + 'Monolog\\Formatter\\LogglyFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php', + 'Monolog\\Formatter\\LogstashFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', + 'Monolog\\Formatter\\MongoDBFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php', + 'Monolog\\Formatter\\NormalizerFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', + 'Monolog\\Formatter\\ScalarFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php', + 'Monolog\\Formatter\\WildfireFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', + 'Monolog\\Handler\\AbstractHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', + 'Monolog\\Handler\\AbstractProcessingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', + 'Monolog\\Handler\\AbstractSyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php', + 'Monolog\\Handler\\AmqpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', + 'Monolog\\Handler\\BrowserConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php', + 'Monolog\\Handler\\BufferHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', + 'Monolog\\Handler\\ChromePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', + 'Monolog\\Handler\\CouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', + 'Monolog\\Handler\\CubeHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', + 'Monolog\\Handler\\Curl\\Util' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php', + 'Monolog\\Handler\\DeduplicationHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php', + 'Monolog\\Handler\\DoctrineCouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', + 'Monolog\\Handler\\DynamoDbHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php', + 'Monolog\\Handler\\ElasticSearchHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php', + 'Monolog\\Handler\\ErrorLogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', + 'Monolog\\Handler\\FilterHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php', + 'Monolog\\Handler\\FingersCrossedHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', + 'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', + 'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', + 'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', + 'Monolog\\Handler\\FirePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', + 'Monolog\\Handler\\FleepHookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php', + 'Monolog\\Handler\\FlowdockHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php', + 'Monolog\\Handler\\GelfHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', + 'Monolog\\Handler\\GroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', + 'Monolog\\Handler\\HandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', + 'Monolog\\Handler\\HandlerWrapper' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php', + 'Monolog\\Handler\\HipChatHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HipChatHandler.php', + 'Monolog\\Handler\\IFTTTHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php', + 'Monolog\\Handler\\LogEntriesHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php', + 'Monolog\\Handler\\LogglyHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php', + 'Monolog\\Handler\\MailHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', + 'Monolog\\Handler\\MandrillHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php', + 'Monolog\\Handler\\MissingExtensionException' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', + 'Monolog\\Handler\\MongoDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', + 'Monolog\\Handler\\NativeMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', + 'Monolog\\Handler\\NewRelicHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', + 'Monolog\\Handler\\NullHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', + 'Monolog\\Handler\\PHPConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php', + 'Monolog\\Handler\\PsrHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php', + 'Monolog\\Handler\\PushoverHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', + 'Monolog\\Handler\\RavenHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RavenHandler.php', + 'Monolog\\Handler\\RedisHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', + 'Monolog\\Handler\\RollbarHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php', + 'Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', + 'Monolog\\Handler\\SamplingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', + 'Monolog\\Handler\\SlackHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', + 'Monolog\\Handler\\SlackWebhookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', + 'Monolog\\Handler\\Slack\\SlackRecord' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', + 'Monolog\\Handler\\SlackbotHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php', + 'Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', + 'Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', + 'Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', + 'Monolog\\Handler\\SyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', + 'Monolog\\Handler\\SyslogUdpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php', + 'Monolog\\Handler\\SyslogUdp\\UdpSocket' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php', + 'Monolog\\Handler\\TestHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', + 'Monolog\\Handler\\WhatFailureGroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php', + 'Monolog\\Handler\\ZendMonitorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', + 'Monolog\\Logger' => $vendorDir . '/monolog/monolog/src/Monolog/Logger.php', + 'Monolog\\Processor\\GitProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php', + 'Monolog\\Processor\\IntrospectionProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', + 'Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', + 'Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', + 'Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\MercurialProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', + 'Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', + 'Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', + 'Monolog\\Processor\\TagProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', + 'Monolog\\Processor\\UidProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', + 'Monolog\\Processor\\WebProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', + 'Monolog\\Registry' => $vendorDir . '/monolog/monolog/src/Monolog/Registry.php', + 'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/Assert.php', + 'PHPUnit\\Framework\\AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/AssertionFailedError.php', + 'PHPUnit\\Framework\\BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php', + 'PHPUnit\\Framework\\Test' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/Test.php', + 'PHPUnit\\Framework\\TestCase' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestCase.php', + 'PHPUnit\\Framework\\TestListener' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestListener.php', + 'PHPUnit\\Framework\\TestSuite' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestSuite.php', + 'PHPUnit_Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php', + 'PHPUnit_Extensions_GroupTestSuite' => $vendorDir . '/phpunit/phpunit/src/Extensions/GroupTestSuite.php', + 'PHPUnit_Extensions_PhptTestCase' => $vendorDir . '/phpunit/phpunit/src/Extensions/PhptTestCase.php', + 'PHPUnit_Extensions_PhptTestSuite' => $vendorDir . '/phpunit/phpunit/src/Extensions/PhptTestSuite.php', + 'PHPUnit_Extensions_RepeatedTest' => $vendorDir . '/phpunit/phpunit/src/Extensions/RepeatedTest.php', + 'PHPUnit_Extensions_TestDecorator' => $vendorDir . '/phpunit/phpunit/src/Extensions/TestDecorator.php', + 'PHPUnit_Extensions_TicketListener' => $vendorDir . '/phpunit/phpunit/src/Extensions/TicketListener.php', + 'PHPUnit_Framework_Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php', + 'PHPUnit_Framework_AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/AssertionFailedError.php', + 'PHPUnit_Framework_BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/BaseTestListener.php', + 'PHPUnit_Framework_CodeCoverageException' => $vendorDir . '/phpunit/phpunit/src/Framework/CodeCoverageException.php', + 'PHPUnit_Framework_Constraint' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint.php', + 'PHPUnit_Framework_Constraint_And' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/And.php', + 'PHPUnit_Framework_Constraint_ArrayHasKey' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', + 'PHPUnit_Framework_Constraint_ArraySubset' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', + 'PHPUnit_Framework_Constraint_Attribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', + 'PHPUnit_Framework_Constraint_Callback' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', + 'PHPUnit_Framework_Constraint_ClassHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', + 'PHPUnit_Framework_Constraint_ClassHasStaticAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', + 'PHPUnit_Framework_Constraint_Composite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', + 'PHPUnit_Framework_Constraint_Count' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Count.php', + 'PHPUnit_Framework_Constraint_DirectoryExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php', + 'PHPUnit_Framework_Constraint_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', + 'PHPUnit_Framework_Constraint_ExceptionCode' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', + 'PHPUnit_Framework_Constraint_ExceptionMessage' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', + 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php', + 'PHPUnit_Framework_Constraint_FileExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', + 'PHPUnit_Framework_Constraint_GreaterThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', + 'PHPUnit_Framework_Constraint_IsAnything' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', + 'PHPUnit_Framework_Constraint_IsEmpty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', + 'PHPUnit_Framework_Constraint_IsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', + 'PHPUnit_Framework_Constraint_IsFalse' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', + 'PHPUnit_Framework_Constraint_IsFinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php', + 'PHPUnit_Framework_Constraint_IsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', + 'PHPUnit_Framework_Constraint_IsInfinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php', + 'PHPUnit_Framework_Constraint_IsInstanceOf' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', + 'PHPUnit_Framework_Constraint_IsJson' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', + 'PHPUnit_Framework_Constraint_IsNan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php', + 'PHPUnit_Framework_Constraint_IsNull' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', + 'PHPUnit_Framework_Constraint_IsReadable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php', + 'PHPUnit_Framework_Constraint_IsTrue' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', + 'PHPUnit_Framework_Constraint_IsType' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', + 'PHPUnit_Framework_Constraint_IsWritable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php', + 'PHPUnit_Framework_Constraint_JsonMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', + 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php', + 'PHPUnit_Framework_Constraint_LessThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', + 'PHPUnit_Framework_Constraint_Not' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Not.php', + 'PHPUnit_Framework_Constraint_ObjectHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', + 'PHPUnit_Framework_Constraint_Or' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Or.php', + 'PHPUnit_Framework_Constraint_PCREMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php', + 'PHPUnit_Framework_Constraint_SameSize' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', + 'PHPUnit_Framework_Constraint_StringContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', + 'PHPUnit_Framework_Constraint_StringEndsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', + 'PHPUnit_Framework_Constraint_StringMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringMatches.php', + 'PHPUnit_Framework_Constraint_StringStartsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', + 'PHPUnit_Framework_Constraint_TraversableContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', + 'PHPUnit_Framework_Constraint_TraversableContainsOnly' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', + 'PHPUnit_Framework_Constraint_Xor' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Xor.php', + 'PHPUnit_Framework_CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.php', + 'PHPUnit_Framework_Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Error.php', + 'PHPUnit_Framework_Error_Deprecated' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', + 'PHPUnit_Framework_Error_Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php', + 'PHPUnit_Framework_Error_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php', + 'PHPUnit_Framework_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception.php', + 'PHPUnit_Framework_ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', + 'PHPUnit_Framework_ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', + 'PHPUnit_Framework_IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php', + 'PHPUnit_Framework_IncompleteTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', + 'PHPUnit_Framework_IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', + 'PHPUnit_Framework_InvalidCoversTargetException' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', + 'PHPUnit_Framework_MissingCoversAnnotationException' => $vendorDir . '/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.php', + 'PHPUnit_Framework_MockObject_BadMethodCallException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php', + 'PHPUnit_Framework_MockObject_Builder_Identity' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php', + 'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php', + 'PHPUnit_Framework_MockObject_Builder_Match' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php', + 'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php', + 'PHPUnit_Framework_MockObject_Builder_Namespace' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php', + 'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php', + 'PHPUnit_Framework_MockObject_Builder_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php', + 'PHPUnit_Framework_MockObject_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php', + 'PHPUnit_Framework_MockObject_Generator' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php', + 'PHPUnit_Framework_MockObject_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php', + 'PHPUnit_Framework_MockObject_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php', + 'PHPUnit_Framework_MockObject_Invocation_Object' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php', + 'PHPUnit_Framework_MockObject_Invocation_Static' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php', + 'PHPUnit_Framework_MockObject_Invokable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php', + 'PHPUnit_Framework_MockObject_Matcher' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php', + 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php', + 'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php', + 'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php', + 'PHPUnit_Framework_MockObject_Matcher_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php', + 'PHPUnit_Framework_MockObject_Matcher_MethodName' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php', + 'PHPUnit_Framework_MockObject_Matcher_Parameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php', + 'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php', + 'PHPUnit_Framework_MockObject_MockBuilder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php', + 'PHPUnit_Framework_MockObject_MockObject' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php', + 'PHPUnit_Framework_MockObject_RuntimeException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php', + 'PHPUnit_Framework_MockObject_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php', + 'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php', + 'PHPUnit_Framework_MockObject_Stub_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php', + 'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php', + 'PHPUnit_Framework_MockObject_Stub_Return' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnReference' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnReference.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php', + 'PHPUnit_Framework_MockObject_Verifiable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php', + 'PHPUnit_Framework_OutputError' => $vendorDir . '/phpunit/phpunit/src/Framework/OutputError.php', + 'PHPUnit_Framework_RiskyTest' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTest.php', + 'PHPUnit_Framework_RiskyTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTestError.php', + 'PHPUnit_Framework_SelfDescribing' => $vendorDir . '/phpunit/phpunit/src/Framework/SelfDescribing.php', + 'PHPUnit_Framework_SkippedTest' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTest.php', + 'PHPUnit_Framework_SkippedTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', + 'PHPUnit_Framework_SkippedTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestError.php', + 'PHPUnit_Framework_SkippedTestSuiteError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', + 'PHPUnit_Framework_SyntheticError' => $vendorDir . '/phpunit/phpunit/src/Framework/SyntheticError.php', + 'PHPUnit_Framework_Test' => $vendorDir . '/phpunit/phpunit/src/Framework/Test.php', + 'PHPUnit_Framework_TestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/TestCase.php', + 'PHPUnit_Framework_TestFailure' => $vendorDir . '/phpunit/phpunit/src/Framework/TestFailure.php', + 'PHPUnit_Framework_TestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListener.php', + 'PHPUnit_Framework_TestResult' => $vendorDir . '/phpunit/phpunit/src/Framework/TestResult.php', + 'PHPUnit_Framework_TestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite.php', + 'PHPUnit_Framework_TestSuite_DataProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php', + 'PHPUnit_Framework_UnintentionallyCoveredCodeError' => $vendorDir . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', + 'PHPUnit_Framework_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Warning.php', + 'PHPUnit_Framework_WarningTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/WarningTestCase.php', + 'PHPUnit_Runner_BaseTestRunner' => $vendorDir . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', + 'PHPUnit_Runner_Exception' => $vendorDir . '/phpunit/phpunit/src/Runner/Exception.php', + 'PHPUnit_Runner_Filter_Factory' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Factory.php', + 'PHPUnit_Runner_Filter_GroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group.php', + 'PHPUnit_Runner_Filter_Group_Exclude' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php', + 'PHPUnit_Runner_Filter_Group_Include' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group/Include.php', + 'PHPUnit_Runner_Filter_Test' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Test.php', + 'PHPUnit_Runner_StandardTestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', + 'PHPUnit_Runner_TestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', + 'PHPUnit_Runner_Version' => $vendorDir . '/phpunit/phpunit/src/Runner/Version.php', + 'PHPUnit_TextUI_Command' => $vendorDir . '/phpunit/phpunit/src/TextUI/Command.php', + 'PHPUnit_TextUI_ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', + 'PHPUnit_TextUI_TestRunner' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestRunner.php', + 'PHPUnit_Util_Blacklist' => $vendorDir . '/phpunit/phpunit/src/Util/Blacklist.php', + 'PHPUnit_Util_Configuration' => $vendorDir . '/phpunit/phpunit/src/Util/Configuration.php', + 'PHPUnit_Util_ConfigurationGenerator' => $vendorDir . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php', + 'PHPUnit_Util_ErrorHandler' => $vendorDir . '/phpunit/phpunit/src/Util/ErrorHandler.php', + 'PHPUnit_Util_Fileloader' => $vendorDir . '/phpunit/phpunit/src/Util/Fileloader.php', + 'PHPUnit_Util_Filesystem' => $vendorDir . '/phpunit/phpunit/src/Util/Filesystem.php', + 'PHPUnit_Util_Filter' => $vendorDir . '/phpunit/phpunit/src/Util/Filter.php', + 'PHPUnit_Util_Getopt' => $vendorDir . '/phpunit/phpunit/src/Util/Getopt.php', + 'PHPUnit_Util_GlobalState' => $vendorDir . '/phpunit/phpunit/src/Util/GlobalState.php', + 'PHPUnit_Util_InvalidArgumentHelper' => $vendorDir . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', + 'PHPUnit_Util_Log_JSON' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JSON.php', + 'PHPUnit_Util_Log_JUnit' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JUnit.php', + 'PHPUnit_Util_Log_TAP' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TAP.php', + 'PHPUnit_Util_Log_TeamCity' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TeamCity.php', + 'PHPUnit_Util_PHP' => $vendorDir . '/phpunit/phpunit/src/Util/PHP.php', + 'PHPUnit_Util_PHP_Default' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/Default.php', + 'PHPUnit_Util_PHP_Windows' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/Windows.php', + 'PHPUnit_Util_Printer' => $vendorDir . '/phpunit/phpunit/src/Util/Printer.php', + 'PHPUnit_Util_Regex' => $vendorDir . '/phpunit/phpunit/src/Util/Regex.php', + 'PHPUnit_Util_String' => $vendorDir . '/phpunit/phpunit/src/Util/String.php', + 'PHPUnit_Util_Test' => $vendorDir . '/phpunit/phpunit/src/Util/Test.php', + 'PHPUnit_Util_TestDox_NamePrettifier' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', + 'PHPUnit_Util_TestDox_ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', + 'PHPUnit_Util_TestDox_ResultPrinter_HTML' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php', + 'PHPUnit_Util_TestDox_ResultPrinter_Text' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php', + 'PHPUnit_Util_TestDox_ResultPrinter_XML' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/XML.php', + 'PHPUnit_Util_TestSuiteIterator' => $vendorDir . '/phpunit/phpunit/src/Util/TestSuiteIterator.php', + 'PHPUnit_Util_Type' => $vendorDir . '/phpunit/phpunit/src/Util/Type.php', + 'PHPUnit_Util_XML' => $vendorDir . '/phpunit/phpunit/src/Util/XML.php', + 'PHP_Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php', + 'PHP_Token' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_TokenWithScope' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_TokenWithScopeAndVisibility' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ABSTRACT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AMPERSAND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AND_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ARRAY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ARRAY_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ASYNC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AWAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BACKTICK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BAD_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BOOLEAN_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BOOLEAN_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BOOL_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BREAK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CALLABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CARET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CASE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CATCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLASS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLASS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLASS_NAME_CONSTANT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLONE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COALESCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COMMA' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COMPILER_HALT_OFFSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONCAT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONSTANT_ENCAPSED_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONTINUE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CURLY_OPEN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DEC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DEFAULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DIR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DIV' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DIV_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOC_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOLLAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_QUOTES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ELLIPSIS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ELSE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ELSEIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EMPTY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENCAPSED_AND_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDDECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDFOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDFOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDSWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDWHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_END_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENUM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EQUALS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EVAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EXCLAMATION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EXIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EXTENDS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FINAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FINALLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FUNC_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_GLOBAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_GOTO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_HALT_COMPILER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IMPLEMENTS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INCLUDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INCLUDE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INLINE_HTML' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INSTANCEOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INSTEADOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INTERFACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ISSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_GREATER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_NOT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_NOT_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_SMALLER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_Includes' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_JOIN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LAMBDA_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LAMBDA_CP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LAMBDA_OP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LINE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LIST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LOGICAL_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LOGICAL_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LOGICAL_XOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_METHOD_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MINUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MINUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MOD_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MUL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NAMESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NEW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NS_SEPARATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NUM_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OBJECT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ONUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_TAG_WITH_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PERCENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PIPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PLUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PLUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_POW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_POW_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PRINT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PRIVATE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PROTECTED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PUBLIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_QUESTION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_REQUIRE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_REQUIRE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_RETURN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SEMICOLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SHAPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SPACESHIP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_START_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STATIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STRING_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STRING_VARNAME' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SUPER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_Stream' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream.php', + 'PHP_Token_Stream_CachingFactory' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', + 'PHP_Token_THROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TILDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TRAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TRAIT_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TRY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TYPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TYPELIST_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TYPELIST_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_UNSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_UNSET_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_USE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_USE_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_VAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_VARIABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_WHERE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_WHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_ATTRIBUTE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_CATEGORY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_CATEGORY_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_CHILDREN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_REQUIRED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_TAG_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_TAG_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_TEXT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XOR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_YIELD' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_YIELD_FROM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', + 'Phinx\\Config\\Config' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/Config.php', + 'Phinx\\Config\\ConfigInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php', + 'Phinx\\Config\\NamespaceAwareInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php', + 'Phinx\\Config\\NamespaceAwareTrait' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php', + 'Phinx\\Console\\Command\\AbstractCommand' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php', + 'Phinx\\Console\\Command\\Breakpoint' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php', + 'Phinx\\Console\\Command\\Create' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Create.php', + 'Phinx\\Console\\Command\\Init' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Init.php', + 'Phinx\\Console\\Command\\Migrate' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php', + 'Phinx\\Console\\Command\\Rollback' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php', + 'Phinx\\Console\\Command\\SeedCreate' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php', + 'Phinx\\Console\\Command\\SeedRun' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php', + 'Phinx\\Console\\Command\\Status' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Status.php', + 'Phinx\\Console\\Command\\Test' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Test.php', + 'Phinx\\Console\\PhinxApplication' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php', + 'Phinx\\Db\\Adapter\\AbstractAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.php', + 'Phinx\\Db\\Adapter\\AdapterFactory' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php', + 'Phinx\\Db\\Adapter\\AdapterInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php', + 'Phinx\\Db\\Adapter\\AdapterWrapper' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php', + 'Phinx\\Db\\Adapter\\MysqlAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php', + 'Phinx\\Db\\Adapter\\PdoAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php', + 'Phinx\\Db\\Adapter\\PostgresAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php', + 'Phinx\\Db\\Adapter\\ProxyAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php', + 'Phinx\\Db\\Adapter\\SQLiteAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php', + 'Phinx\\Db\\Adapter\\SqlServerAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php', + 'Phinx\\Db\\Adapter\\TablePrefixAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php', + 'Phinx\\Db\\Adapter\\TimedOutputAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php', + 'Phinx\\Db\\Adapter\\WrapperInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php', + 'Phinx\\Db\\Table' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table.php', + 'Phinx\\Db\\Table\\Column' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Column.php', + 'Phinx\\Db\\Table\\ForeignKey' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php', + 'Phinx\\Db\\Table\\Index' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Index.php', + 'Phinx\\Migration\\AbstractMigration' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php', + 'Phinx\\Migration\\AbstractTemplateCreation' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php', + 'Phinx\\Migration\\CreationInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php', + 'Phinx\\Migration\\IrreversibleMigrationException' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php', + 'Phinx\\Migration\\Manager' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/Manager.php', + 'Phinx\\Migration\\Manager\\Environment' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php', + 'Phinx\\Migration\\MigrationInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.php', + 'Phinx\\Seed\\AbstractSeed' => $vendorDir . '/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php', + 'Phinx\\Seed\\SeedInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Seed/SeedInterface.php', + 'Phinx\\Util\\Util' => $vendorDir . '/robmorgan/phinx/src/Phinx/Util/Util.php', + 'Phinx\\Wrapper\\TextWrapper' => $vendorDir . '/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php', + 'Pimple\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Container.php', + 'Pimple\\Exception\\ExpectedInvokableException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', + 'Pimple\\Exception\\FrozenServiceException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php', + 'Pimple\\Exception\\InvalidServiceIdentifierException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php', + 'Pimple\\Exception\\UnknownIdentifierException' => $vendorDir . '/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php', + 'Pimple\\Psr11\\Container' => $vendorDir . '/pimple/pimple/src/Pimple/Psr11/Container.php', + 'Pimple\\Psr11\\ServiceLocator' => $vendorDir . '/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php', + 'Pimple\\ServiceIterator' => $vendorDir . '/pimple/pimple/src/Pimple/ServiceIterator.php', + 'Pimple\\ServiceProviderInterface' => $vendorDir . '/pimple/pimple/src/Pimple/ServiceProviderInterface.php', + 'Pimple\\Tests\\Fixtures\\Invokable' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php', + 'Pimple\\Tests\\Fixtures\\NonInvokable' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php', + 'Pimple\\Tests\\Fixtures\\PimpleServiceProvider' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php', + 'Pimple\\Tests\\Fixtures\\Service' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php', + 'Pimple\\Tests\\PimpleServiceProviderInterfaceTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php', + 'Pimple\\Tests\\PimpleTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/PimpleTest.php', + 'Pimple\\Tests\\Psr11\\ContainerTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php', + 'Pimple\\Tests\\Psr11\\ServiceLocatorTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php', + 'Pimple\\Tests\\ServiceIteratorTest' => $vendorDir . '/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php', + 'Prophecy\\Argument' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument.php', + 'Prophecy\\Argument\\ArgumentsWildcard' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php', + 'Prophecy\\Argument\\Token\\AnyValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php', + 'Prophecy\\Argument\\Token\\AnyValuesToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php', + 'Prophecy\\Argument\\Token\\ApproximateValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php', + 'Prophecy\\Argument\\Token\\ArrayCountToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php', + 'Prophecy\\Argument\\Token\\ArrayEntryToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php', + 'Prophecy\\Argument\\Token\\ArrayEveryEntryToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php', + 'Prophecy\\Argument\\Token\\CallbackToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php', + 'Prophecy\\Argument\\Token\\ExactValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php', + 'Prophecy\\Argument\\Token\\IdenticalValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php', + 'Prophecy\\Argument\\Token\\LogicalAndToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php', + 'Prophecy\\Argument\\Token\\LogicalNotToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php', + 'Prophecy\\Argument\\Token\\ObjectStateToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php', + 'Prophecy\\Argument\\Token\\StringContainsToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php', + 'Prophecy\\Argument\\Token\\TokenInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php', + 'Prophecy\\Argument\\Token\\TypeToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php', + 'Prophecy\\Call\\Call' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Call/Call.php', + 'Prophecy\\Call\\CallCenter' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Call/CallCenter.php', + 'Prophecy\\Comparator\\ClosureComparator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php', + 'Prophecy\\Comparator\\Factory' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Comparator/Factory.php', + 'Prophecy\\Comparator\\ProphecyComparator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php', + 'Prophecy\\Doubler\\CachedDoubler' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php', + 'Prophecy\\Doubler\\ClassPatch\\ClassPatchInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php', + 'Prophecy\\Doubler\\ClassPatch\\DisableConstructorPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\HhvmExceptionPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\KeywordPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\MagicCallPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\ProphecySubjectPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\ReflectionClassNewInstancePatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php', + 'Prophecy\\Doubler\\ClassPatch\\SplFileInfoPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\TraversablePatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php', + 'Prophecy\\Doubler\\DoubleInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php', + 'Prophecy\\Doubler\\Doubler' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php', + 'Prophecy\\Doubler\\Generator\\ClassCodeGenerator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php', + 'Prophecy\\Doubler\\Generator\\ClassCreator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php', + 'Prophecy\\Doubler\\Generator\\ClassMirror' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php', + 'Prophecy\\Doubler\\Generator\\Node\\ArgumentNode' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php', + 'Prophecy\\Doubler\\Generator\\Node\\ClassNode' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php', + 'Prophecy\\Doubler\\Generator\\Node\\MethodNode' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php', + 'Prophecy\\Doubler\\Generator\\ReflectionInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php', + 'Prophecy\\Doubler\\Generator\\TypeHintReference' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php', + 'Prophecy\\Doubler\\LazyDouble' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php', + 'Prophecy\\Doubler\\NameGenerator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php', + 'Prophecy\\Exception\\Call\\UnexpectedCallException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php', + 'Prophecy\\Exception\\Doubler\\ClassCreatorException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php', + 'Prophecy\\Exception\\Doubler\\ClassMirrorException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php', + 'Prophecy\\Exception\\Doubler\\ClassNotFoundException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php', + 'Prophecy\\Exception\\Doubler\\DoubleException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php', + 'Prophecy\\Exception\\Doubler\\DoublerException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php', + 'Prophecy\\Exception\\Doubler\\InterfaceNotFoundException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php', + 'Prophecy\\Exception\\Doubler\\MethodNotExtendableException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php', + 'Prophecy\\Exception\\Doubler\\MethodNotFoundException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php', + 'Prophecy\\Exception\\Doubler\\ReturnByReferenceException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php', + 'Prophecy\\Exception\\Exception' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Exception.php', + 'Prophecy\\Exception\\InvalidArgumentException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php', + 'Prophecy\\Exception\\Prediction\\AggregateException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php', + 'Prophecy\\Exception\\Prediction\\FailedPredictionException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php', + 'Prophecy\\Exception\\Prediction\\NoCallsException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php', + 'Prophecy\\Exception\\Prediction\\PredictionException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php', + 'Prophecy\\Exception\\Prediction\\UnexpectedCallsCountException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php', + 'Prophecy\\Exception\\Prediction\\UnexpectedCallsException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php', + 'Prophecy\\Exception\\Prophecy\\MethodProphecyException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php', + 'Prophecy\\Exception\\Prophecy\\ObjectProphecyException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php', + 'Prophecy\\Exception\\Prophecy\\ProphecyException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php', + 'Prophecy\\PhpDocumentor\\ClassAndInterfaceTagRetriever' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php', + 'Prophecy\\PhpDocumentor\\ClassTagRetriever' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php', + 'Prophecy\\PhpDocumentor\\LegacyClassTagRetriever' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php', + 'Prophecy\\PhpDocumentor\\MethodTagRetrieverInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php', + 'Prophecy\\Prediction\\CallPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php', + 'Prophecy\\Prediction\\CallTimesPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php', + 'Prophecy\\Prediction\\CallbackPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php', + 'Prophecy\\Prediction\\NoCallsPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php', + 'Prophecy\\Prediction\\PredictionInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php', + 'Prophecy\\Promise\\CallbackPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php', + 'Prophecy\\Promise\\PromiseInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php', + 'Prophecy\\Promise\\ReturnArgumentPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php', + 'Prophecy\\Promise\\ReturnPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php', + 'Prophecy\\Promise\\ThrowPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php', + 'Prophecy\\Prophecy\\MethodProphecy' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php', + 'Prophecy\\Prophecy\\ObjectProphecy' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php', + 'Prophecy\\Prophecy\\ProphecyInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php', + 'Prophecy\\Prophecy\\ProphecySubjectInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php', + 'Prophecy\\Prophecy\\Revealer' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php', + 'Prophecy\\Prophecy\\RevealerInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php', + 'Prophecy\\Prophet' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophet.php', + 'Prophecy\\Util\\ExportUtil' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php', + 'Prophecy\\Util\\StringUtil' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Util/StringUtil.php', + 'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => $vendorDir . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\SimpleCache\\CacheException' => $vendorDir . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => $vendorDir . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => $vendorDir . '/psr/simple-cache/src/InvalidArgumentException.php', + 'RKA\\Middleware\\IpAddress' => $vendorDir . '/akrabat/rka-ip-address-middleware/src/IpAddress.php', + 'Ramsey\\Uuid\\BinaryUtils' => $vendorDir . '/ramsey/uuid/src/BinaryUtils.php', + 'Ramsey\\Uuid\\Builder\\DefaultUuidBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/DefaultUuidBuilder.php', + 'Ramsey\\Uuid\\Builder\\DegradedUuidBuilder' => $vendorDir . '/ramsey/uuid/src/Builder/DegradedUuidBuilder.php', + 'Ramsey\\Uuid\\Builder\\UuidBuilderInterface' => $vendorDir . '/ramsey/uuid/src/Builder/UuidBuilderInterface.php', + 'Ramsey\\Uuid\\Codec\\CodecInterface' => $vendorDir . '/ramsey/uuid/src/Codec/CodecInterface.php', + 'Ramsey\\Uuid\\Codec\\GuidStringCodec' => $vendorDir . '/ramsey/uuid/src/Codec/GuidStringCodec.php', + 'Ramsey\\Uuid\\Codec\\OrderedTimeCodec' => $vendorDir . '/ramsey/uuid/src/Codec/OrderedTimeCodec.php', + 'Ramsey\\Uuid\\Codec\\StringCodec' => $vendorDir . '/ramsey/uuid/src/Codec/StringCodec.php', + 'Ramsey\\Uuid\\Codec\\TimestampFirstCombCodec' => $vendorDir . '/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php', + 'Ramsey\\Uuid\\Codec\\TimestampLastCombCodec' => $vendorDir . '/ramsey/uuid/src/Codec/TimestampLastCombCodec.php', + 'Ramsey\\Uuid\\Converter\\NumberConverterInterface' => $vendorDir . '/ramsey/uuid/src/Converter/NumberConverterInterface.php', + 'Ramsey\\Uuid\\Converter\\Number\\BigNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/BigNumberConverter.php', + 'Ramsey\\Uuid\\Converter\\Number\\DegradedNumberConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php', + 'Ramsey\\Uuid\\Converter\\TimeConverterInterface' => $vendorDir . '/ramsey/uuid/src/Converter/TimeConverterInterface.php', + 'Ramsey\\Uuid\\Converter\\Time\\BigNumberTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php', + 'Ramsey\\Uuid\\Converter\\Time\\DegradedTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php', + 'Ramsey\\Uuid\\Converter\\Time\\PhpTimeConverter' => $vendorDir . '/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php', + 'Ramsey\\Uuid\\DegradedUuid' => $vendorDir . '/ramsey/uuid/src/DegradedUuid.php', + 'Ramsey\\Uuid\\Exception\\InvalidUuidStringException' => $vendorDir . '/ramsey/uuid/src/Exception/InvalidUuidStringException.php', + 'Ramsey\\Uuid\\Exception\\UnsatisfiedDependencyException' => $vendorDir . '/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php', + 'Ramsey\\Uuid\\Exception\\UnsupportedOperationException' => $vendorDir . '/ramsey/uuid/src/Exception/UnsupportedOperationException.php', + 'Ramsey\\Uuid\\FeatureSet' => $vendorDir . '/ramsey/uuid/src/FeatureSet.php', + 'Ramsey\\Uuid\\Generator\\CombGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/CombGenerator.php', + 'Ramsey\\Uuid\\Generator\\DefaultTimeGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/DefaultTimeGenerator.php', + 'Ramsey\\Uuid\\Generator\\MtRandGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/MtRandGenerator.php', + 'Ramsey\\Uuid\\Generator\\OpenSslGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/OpenSslGenerator.php', + 'Ramsey\\Uuid\\Generator\\PeclUuidRandomGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php', + 'Ramsey\\Uuid\\Generator\\PeclUuidTimeGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php', + 'Ramsey\\Uuid\\Generator\\RandomBytesGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/RandomBytesGenerator.php', + 'Ramsey\\Uuid\\Generator\\RandomGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/RandomGeneratorFactory.php', + 'Ramsey\\Uuid\\Generator\\RandomGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/RandomGeneratorInterface.php', + 'Ramsey\\Uuid\\Generator\\RandomLibAdapter' => $vendorDir . '/ramsey/uuid/src/Generator/RandomLibAdapter.php', + 'Ramsey\\Uuid\\Generator\\SodiumRandomGenerator' => $vendorDir . '/ramsey/uuid/src/Generator/SodiumRandomGenerator.php', + 'Ramsey\\Uuid\\Generator\\TimeGeneratorFactory' => $vendorDir . '/ramsey/uuid/src/Generator/TimeGeneratorFactory.php', + 'Ramsey\\Uuid\\Generator\\TimeGeneratorInterface' => $vendorDir . '/ramsey/uuid/src/Generator/TimeGeneratorInterface.php', + 'Ramsey\\Uuid\\Provider\\NodeProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/NodeProviderInterface.php', + 'Ramsey\\Uuid\\Provider\\Node\\FallbackNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php', + 'Ramsey\\Uuid\\Provider\\Node\\RandomNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php', + 'Ramsey\\Uuid\\Provider\\Node\\SystemNodeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php', + 'Ramsey\\Uuid\\Provider\\TimeProviderInterface' => $vendorDir . '/ramsey/uuid/src/Provider/TimeProviderInterface.php', + 'Ramsey\\Uuid\\Provider\\Time\\FixedTimeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php', + 'Ramsey\\Uuid\\Provider\\Time\\SystemTimeProvider' => $vendorDir . '/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php', + 'Ramsey\\Uuid\\Uuid' => $vendorDir . '/ramsey/uuid/src/Uuid.php', + 'Ramsey\\Uuid\\UuidFactory' => $vendorDir . '/ramsey/uuid/src/UuidFactory.php', + 'Ramsey\\Uuid\\UuidFactoryInterface' => $vendorDir . '/ramsey/uuid/src/UuidFactoryInterface.php', + 'Ramsey\\Uuid\\UuidInterface' => $vendorDir . '/ramsey/uuid/src/UuidInterface.php', + 'RateLimit\\AbstractRateLimiter' => $vendorDir . '/wellingguzman/rate-limit/src/AbstractRateLimiter.php', + 'RateLimit\\Exception\\ExceptionInterface' => $vendorDir . '/wellingguzman/rate-limit/src/Exception/ExceptionInterface.php', + 'RateLimit\\Exception\\RateLimitExceededException' => $vendorDir . '/wellingguzman/rate-limit/src/Exception/RateLimitExceededException.php', + 'RateLimit\\InMemoryRateLimiter' => $vendorDir . '/wellingguzman/rate-limit/src/InMemoryRateLimiter.php', + 'RateLimit\\Middleware\\Identity\\AbstractIdentityResolver' => $vendorDir . '/wellingguzman/rate-limit/src/Middleware/Identity/AbstractIdentityResolver.php', + 'RateLimit\\Middleware\\Identity\\IdentityResolverInterface' => $vendorDir . '/wellingguzman/rate-limit/src/Middleware/Identity/IdentityResolverInterface.php', + 'RateLimit\\Middleware\\Identity\\IpAddressIdentityResolver' => $vendorDir . '/wellingguzman/rate-limit/src/Middleware/Identity/IpAddressIdentityResolver.php', + 'RateLimit\\Middleware\\Options' => $vendorDir . '/wellingguzman/rate-limit/src/Middleware/Options.php', + 'RateLimit\\Middleware\\RateLimitMiddleware' => $vendorDir . '/wellingguzman/rate-limit/src/Middleware/RateLimitMiddleware.php', + 'RateLimit\\RateLimiterFactory' => $vendorDir . '/wellingguzman/rate-limit/src/RateLimiterFactory.php', + 'RateLimit\\RateLimiterInterface' => $vendorDir . '/wellingguzman/rate-limit/src/RateLimiterInterface.php', + 'RateLimit\\RedisRateLimiter' => $vendorDir . '/wellingguzman/rate-limit/src/RedisRateLimiter.php', + 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php', + 'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\HHVM' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/HHVM.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Xdebug.php', + 'SebastianBergmann\\CodeCoverage\\Exception' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Exception.php', + 'SebastianBergmann\\CodeCoverage\\Filter' => $vendorDir . '/phpunit/php-code-coverage/src/Filter.php', + 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php', + 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => $vendorDir . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Builder.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Node\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Node/File.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Iterator.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Clover.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Crap4j.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', + 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => $vendorDir . '/phpunit/php-code-coverage/src/Report/PHP.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Text' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Text.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', + 'SebastianBergmann\\CodeCoverage\\RuntimeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php', + 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', + 'SebastianBergmann\\CodeCoverage\\Util' => $vendorDir . '/phpunit/php-code-coverage/src/Util.php', + 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => $vendorDir . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', + 'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php', + 'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php', + 'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php', + 'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php', + 'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php', + 'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php', + 'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php', + 'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php', + 'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php', + 'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php', + 'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php', + 'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php', + 'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php', + 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php', + 'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php', + 'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php', + 'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php', + 'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php', + 'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => $vendorDir . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php', + 'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', + 'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', + 'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php', + 'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php', + 'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php', + 'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php', + 'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php', + 'SebastianBergmann\\GlobalState\\Blacklist' => $vendorDir . '/sebastian/global-state/src/Blacklist.php', + 'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php', + 'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/Exception.php', + 'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php', + 'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/RuntimeException.php', + 'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php', + 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => $vendorDir . '/sebastian/object-enumerator/src/Enumerator.php', + 'SebastianBergmann\\ObjectEnumerator\\Exception' => $vendorDir . '/sebastian/object-enumerator/src/Exception.php', + 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => $vendorDir . '/sebastian/object-enumerator/src/InvalidArgumentException.php', + 'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php', + 'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php', + 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php', + 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => $vendorDir . '/sebastian/resource-operations/src/ResourceOperations.php', + 'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php', + 'Slim\\App' => $vendorDir . '/slim/slim/Slim/App.php', + 'Slim\\CallableResolver' => $vendorDir . '/slim/slim/Slim/CallableResolver.php', + 'Slim\\CallableResolverAwareTrait' => $vendorDir . '/slim/slim/Slim/CallableResolverAwareTrait.php', + 'Slim\\Collection' => $vendorDir . '/slim/slim/Slim/Collection.php', + 'Slim\\Container' => $vendorDir . '/slim/slim/Slim/Container.php', + 'Slim\\DefaultServicesProvider' => $vendorDir . '/slim/slim/Slim/DefaultServicesProvider.php', + 'Slim\\DeferredCallable' => $vendorDir . '/slim/slim/Slim/DeferredCallable.php', + 'Slim\\Exception\\ContainerException' => $vendorDir . '/slim/slim/Slim/Exception/ContainerException.php', + 'Slim\\Exception\\ContainerValueNotFoundException' => $vendorDir . '/slim/slim/Slim/Exception/ContainerValueNotFoundException.php', + 'Slim\\Exception\\InvalidMethodException' => $vendorDir . '/slim/slim/Slim/Exception/InvalidMethodException.php', + 'Slim\\Exception\\MethodNotAllowedException' => $vendorDir . '/slim/slim/Slim/Exception/MethodNotAllowedException.php', + 'Slim\\Exception\\NotFoundException' => $vendorDir . '/slim/slim/Slim/Exception/NotFoundException.php', + 'Slim\\Exception\\SlimException' => $vendorDir . '/slim/slim/Slim/Exception/SlimException.php', + 'Slim\\Handlers\\AbstractError' => $vendorDir . '/slim/slim/Slim/Handlers/AbstractError.php', + 'Slim\\Handlers\\AbstractHandler' => $vendorDir . '/slim/slim/Slim/Handlers/AbstractHandler.php', + 'Slim\\Handlers\\Error' => $vendorDir . '/slim/slim/Slim/Handlers/Error.php', + 'Slim\\Handlers\\NotAllowed' => $vendorDir . '/slim/slim/Slim/Handlers/NotAllowed.php', + 'Slim\\Handlers\\NotFound' => $vendorDir . '/slim/slim/Slim/Handlers/NotFound.php', + 'Slim\\Handlers\\PhpError' => $vendorDir . '/slim/slim/Slim/Handlers/PhpError.php', + 'Slim\\Handlers\\Strategies\\RequestResponse' => $vendorDir . '/slim/slim/Slim/Handlers/Strategies/RequestResponse.php', + 'Slim\\Handlers\\Strategies\\RequestResponseArgs' => $vendorDir . '/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php', + 'Slim\\Http\\Body' => $vendorDir . '/slim/slim/Slim/Http/Body.php', + 'Slim\\Http\\Cookies' => $vendorDir . '/slim/slim/Slim/Http/Cookies.php', + 'Slim\\Http\\Environment' => $vendorDir . '/slim/slim/Slim/Http/Environment.php', + 'Slim\\Http\\Headers' => $vendorDir . '/slim/slim/Slim/Http/Headers.php', + 'Slim\\Http\\Message' => $vendorDir . '/slim/slim/Slim/Http/Message.php', + 'Slim\\Http\\Request' => $vendorDir . '/slim/slim/Slim/Http/Request.php', + 'Slim\\Http\\RequestBody' => $vendorDir . '/slim/slim/Slim/Http/RequestBody.php', + 'Slim\\Http\\Response' => $vendorDir . '/slim/slim/Slim/Http/Response.php', + 'Slim\\Http\\Stream' => $vendorDir . '/slim/slim/Slim/Http/Stream.php', + 'Slim\\Http\\UploadedFile' => $vendorDir . '/slim/slim/Slim/Http/UploadedFile.php', + 'Slim\\Http\\Uri' => $vendorDir . '/slim/slim/Slim/Http/Uri.php', + 'Slim\\Interfaces\\CallableResolverInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/CallableResolverInterface.php', + 'Slim\\Interfaces\\CollectionInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/CollectionInterface.php', + 'Slim\\Interfaces\\Http\\CookiesInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/Http/CookiesInterface.php', + 'Slim\\Interfaces\\Http\\EnvironmentInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/Http/EnvironmentInterface.php', + 'Slim\\Interfaces\\Http\\HeadersInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/Http/HeadersInterface.php', + 'Slim\\Interfaces\\InvocationStrategyInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/InvocationStrategyInterface.php', + 'Slim\\Interfaces\\RouteGroupInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/RouteGroupInterface.php', + 'Slim\\Interfaces\\RouteInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/RouteInterface.php', + 'Slim\\Interfaces\\RouterInterface' => $vendorDir . '/slim/slim/Slim/Interfaces/RouterInterface.php', + 'Slim\\MiddlewareAwareTrait' => $vendorDir . '/slim/slim/Slim/MiddlewareAwareTrait.php', + 'Slim\\Routable' => $vendorDir . '/slim/slim/Slim/Routable.php', + 'Slim\\Route' => $vendorDir . '/slim/slim/Slim/Route.php', + 'Slim\\RouteGroup' => $vendorDir . '/slim/slim/Slim/RouteGroup.php', + 'Slim\\Router' => $vendorDir . '/slim/slim/Slim/Router.php', + 'Slim\\Views\\Twig' => $vendorDir . '/slim/twig-view/src/Twig.php', + 'Slim\\Views\\TwigExtension' => $vendorDir . '/slim/twig-view/src/TwigExtension.php', + 'Symfony\\Component\\Config\\ConfigCache' => $vendorDir . '/symfony/config/ConfigCache.php', + 'Symfony\\Component\\Config\\ConfigCacheFactory' => $vendorDir . '/symfony/config/ConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => $vendorDir . '/symfony/config/ConfigCacheFactoryInterface.php', + 'Symfony\\Component\\Config\\ConfigCacheInterface' => $vendorDir . '/symfony/config/ConfigCacheInterface.php', + 'Symfony\\Component\\Config\\Definition\\ArrayNode' => $vendorDir . '/symfony/config/Definition/ArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\BaseNode' => $vendorDir . '/symfony/config/Definition/BaseNode.php', + 'Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => $vendorDir . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/MergeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NodeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => $vendorDir . '/symfony/config/Definition/Builder/NodeParentInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NormalizationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NumericNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => $vendorDir . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/TreeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ValidationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => $vendorDir . '/symfony/config/Definition/ConfigurationInterface.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\EnumNode' => $vendorDir . '/symfony/config/Definition/EnumNode.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => $vendorDir . '/symfony/config/Definition/Exception/DuplicateKeyException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => $vendorDir . '/symfony/config/Definition/Exception/Exception.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => $vendorDir . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidTypeException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php', + 'Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php', + 'Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php', + 'Symfony\\Component\\Config\\Definition\\NodeInterface' => $vendorDir . '/symfony/config/Definition/NodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\NumericNode' => $vendorDir . '/symfony/config/Definition/NumericNode.php', + 'Symfony\\Component\\Config\\Definition\\Processor' => $vendorDir . '/symfony/config/Definition/Processor.php', + 'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => $vendorDir . '/symfony/config/Definition/PrototypeNodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => $vendorDir . '/symfony/config/Definition/PrototypedArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\ScalarNode' => $vendorDir . '/symfony/config/Definition/ScalarNode.php', + 'Symfony\\Component\\Config\\Definition\\VariableNode' => $vendorDir . '/symfony/config/Definition/VariableNode.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => $vendorDir . '/symfony/config/Exception/FileLoaderLoadException.php', + 'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => $vendorDir . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', + 'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php', + 'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php', + 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php', + 'Symfony\\Component\\Config\\Loader\\FileLoader' => $vendorDir . '/symfony/config/Loader/FileLoader.php', + 'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/config/Loader/GlobFileLoader.php', + 'Symfony\\Component\\Config\\Loader\\Loader' => $vendorDir . '/symfony/config/Loader/Loader.php', + 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => $vendorDir . '/symfony/config/Loader/LoaderInterface.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolver' => $vendorDir . '/symfony/config/Loader/LoaderResolver.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => $vendorDir . '/symfony/config/Loader/LoaderResolverInterface.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => $vendorDir . '/symfony/config/ResourceCheckerConfigCache.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => $vendorDir . '/symfony/config/ResourceCheckerConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ResourceCheckerInterface' => $vendorDir . '/symfony/config/ResourceCheckerInterface.php', + 'Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => $vendorDir . '/symfony/config/Resource/ClassExistenceResource.php', + 'Symfony\\Component\\Config\\Resource\\ComposerResource' => $vendorDir . '/symfony/config/Resource/ComposerResource.php', + 'Symfony\\Component\\Config\\Resource\\DirectoryResource' => $vendorDir . '/symfony/config/Resource/DirectoryResource.php', + 'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => $vendorDir . '/symfony/config/Resource/FileExistenceResource.php', + 'Symfony\\Component\\Config\\Resource\\FileResource' => $vendorDir . '/symfony/config/Resource/FileResource.php', + 'Symfony\\Component\\Config\\Resource\\GlobResource' => $vendorDir . '/symfony/config/Resource/GlobResource.php', + 'Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => $vendorDir . '/symfony/config/Resource/ReflectionClassResource.php', + 'Symfony\\Component\\Config\\Resource\\ResourceInterface' => $vendorDir . '/symfony/config/Resource/ResourceInterface.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceChecker.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceInterface.php', + 'Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => $vendorDir . '/symfony/config/Util/Exception/InvalidXmlException.php', + 'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => $vendorDir . '/symfony/config/Util/Exception/XmlParsingException.php', + 'Symfony\\Component\\Config\\Util\\XmlUtils' => $vendorDir . '/symfony/config/Util/XmlUtils.php', + 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php', + 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php', + 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php', + 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Translation\\Catalogue\\AbstractOperation' => $vendorDir . '/symfony/translation/Catalogue/AbstractOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\MergeOperation' => $vendorDir . '/symfony/translation/Catalogue/MergeOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\OperationInterface' => $vendorDir . '/symfony/translation/Catalogue/OperationInterface.php', + 'Symfony\\Component\\Translation\\Catalogue\\TargetOperation' => $vendorDir . '/symfony/translation/Catalogue/TargetOperation.php', + 'Symfony\\Component\\Translation\\Command\\XliffLintCommand' => $vendorDir . '/symfony/translation/Command/XliffLintCommand.php', + 'Symfony\\Component\\Translation\\DataCollectorTranslator' => $vendorDir . '/symfony/translation/DataCollectorTranslator.php', + 'Symfony\\Component\\Translation\\DataCollector\\TranslationDataCollector' => $vendorDir . '/symfony/translation/DataCollector/TranslationDataCollector.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationDumperPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslationDumperPass.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationExtractorPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslationExtractorPass.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslatorPass' => $vendorDir . '/symfony/translation/DependencyInjection/TranslatorPass.php', + 'Symfony\\Component\\Translation\\Dumper\\CsvFileDumper' => $vendorDir . '/symfony/translation/Dumper/CsvFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\DumperInterface' => $vendorDir . '/symfony/translation/Dumper/DumperInterface.php', + 'Symfony\\Component\\Translation\\Dumper\\FileDumper' => $vendorDir . '/symfony/translation/Dumper/FileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\IcuResFileDumper' => $vendorDir . '/symfony/translation/Dumper/IcuResFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\IniFileDumper' => $vendorDir . '/symfony/translation/Dumper/IniFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\JsonFileDumper' => $vendorDir . '/symfony/translation/Dumper/JsonFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\MoFileDumper' => $vendorDir . '/symfony/translation/Dumper/MoFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\PhpFileDumper' => $vendorDir . '/symfony/translation/Dumper/PhpFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\PoFileDumper' => $vendorDir . '/symfony/translation/Dumper/PoFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\QtFileDumper' => $vendorDir . '/symfony/translation/Dumper/QtFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\XliffFileDumper' => $vendorDir . '/symfony/translation/Dumper/XliffFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\YamlFileDumper' => $vendorDir . '/symfony/translation/Dumper/YamlFileDumper.php', + 'Symfony\\Component\\Translation\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/translation/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Translation\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/translation/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Translation\\Exception\\InvalidResourceException' => $vendorDir . '/symfony/translation/Exception/InvalidResourceException.php', + 'Symfony\\Component\\Translation\\Exception\\LogicException' => $vendorDir . '/symfony/translation/Exception/LogicException.php', + 'Symfony\\Component\\Translation\\Exception\\NotFoundResourceException' => $vendorDir . '/symfony/translation/Exception/NotFoundResourceException.php', + 'Symfony\\Component\\Translation\\Exception\\RuntimeException' => $vendorDir . '/symfony/translation/Exception/RuntimeException.php', + 'Symfony\\Component\\Translation\\Extractor\\AbstractFileExtractor' => $vendorDir . '/symfony/translation/Extractor/AbstractFileExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\ChainExtractor' => $vendorDir . '/symfony/translation/Extractor/ChainExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\ExtractorInterface' => $vendorDir . '/symfony/translation/Extractor/ExtractorInterface.php', + 'Symfony\\Component\\Translation\\Extractor\\PhpExtractor' => $vendorDir . '/symfony/translation/Extractor/PhpExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\PhpStringTokenParser' => $vendorDir . '/symfony/translation/Extractor/PhpStringTokenParser.php', + 'Symfony\\Component\\Translation\\Formatter\\ChoiceMessageFormatterInterface' => $vendorDir . '/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php', + 'Symfony\\Component\\Translation\\Formatter\\MessageFormatter' => $vendorDir . '/symfony/translation/Formatter/MessageFormatter.php', + 'Symfony\\Component\\Translation\\Formatter\\MessageFormatterInterface' => $vendorDir . '/symfony/translation/Formatter/MessageFormatterInterface.php', + 'Symfony\\Component\\Translation\\IdentityTranslator' => $vendorDir . '/symfony/translation/IdentityTranslator.php', + 'Symfony\\Component\\Translation\\Interval' => $vendorDir . '/symfony/translation/Interval.php', + 'Symfony\\Component\\Translation\\Loader\\ArrayLoader' => $vendorDir . '/symfony/translation/Loader/ArrayLoader.php', + 'Symfony\\Component\\Translation\\Loader\\CsvFileLoader' => $vendorDir . '/symfony/translation/Loader/CsvFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\FileLoader' => $vendorDir . '/symfony/translation/Loader/FileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader' => $vendorDir . '/symfony/translation/Loader/IcuDatFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IcuResFileLoader' => $vendorDir . '/symfony/translation/Loader/IcuResFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IniFileLoader' => $vendorDir . '/symfony/translation/Loader/IniFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\JsonFileLoader' => $vendorDir . '/symfony/translation/Loader/JsonFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\LoaderInterface' => $vendorDir . '/symfony/translation/Loader/LoaderInterface.php', + 'Symfony\\Component\\Translation\\Loader\\MoFileLoader' => $vendorDir . '/symfony/translation/Loader/MoFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/translation/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\PoFileLoader' => $vendorDir . '/symfony/translation/Loader/PoFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\QtFileLoader' => $vendorDir . '/symfony/translation/Loader/QtFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\XliffFileLoader' => $vendorDir . '/symfony/translation/Loader/XliffFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/translation/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Translation\\LoggingTranslator' => $vendorDir . '/symfony/translation/LoggingTranslator.php', + 'Symfony\\Component\\Translation\\MessageCatalogue' => $vendorDir . '/symfony/translation/MessageCatalogue.php', + 'Symfony\\Component\\Translation\\MessageCatalogueInterface' => $vendorDir . '/symfony/translation/MessageCatalogueInterface.php', + 'Symfony\\Component\\Translation\\MessageSelector' => $vendorDir . '/symfony/translation/MessageSelector.php', + 'Symfony\\Component\\Translation\\MetadataAwareInterface' => $vendorDir . '/symfony/translation/MetadataAwareInterface.php', + 'Symfony\\Component\\Translation\\PluralizationRules' => $vendorDir . '/symfony/translation/PluralizationRules.php', + 'Symfony\\Component\\Translation\\Reader\\TranslationReader' => $vendorDir . '/symfony/translation/Reader/TranslationReader.php', + 'Symfony\\Component\\Translation\\Reader\\TranslationReaderInterface' => $vendorDir . '/symfony/translation/Reader/TranslationReaderInterface.php', + 'Symfony\\Component\\Translation\\Translator' => $vendorDir . '/symfony/translation/Translator.php', + 'Symfony\\Component\\Translation\\TranslatorBagInterface' => $vendorDir . '/symfony/translation/TranslatorBagInterface.php', + 'Symfony\\Component\\Translation\\TranslatorInterface' => $vendorDir . '/symfony/translation/TranslatorInterface.php', + 'Symfony\\Component\\Translation\\Util\\ArrayConverter' => $vendorDir . '/symfony/translation/Util/ArrayConverter.php', + 'Symfony\\Component\\Translation\\Writer\\TranslationWriter' => $vendorDir . '/symfony/translation/Writer/TranslationWriter.php', + 'Symfony\\Component\\Translation\\Writer\\TranslationWriterInterface' => $vendorDir . '/symfony/translation/Writer/TranslationWriterInterface.php', + 'Symfony\\Component\\Validator\\Constraint' => $vendorDir . '/symfony/validator/Constraint.php', + 'Symfony\\Component\\Validator\\ConstraintValidator' => $vendorDir . '/symfony/validator/ConstraintValidator.php', + 'Symfony\\Component\\Validator\\ConstraintValidatorFactory' => $vendorDir . '/symfony/validator/ConstraintValidatorFactory.php', + 'Symfony\\Component\\Validator\\ConstraintValidatorFactoryInterface' => $vendorDir . '/symfony/validator/ConstraintValidatorFactoryInterface.php', + 'Symfony\\Component\\Validator\\ConstraintValidatorInterface' => $vendorDir . '/symfony/validator/ConstraintValidatorInterface.php', + 'Symfony\\Component\\Validator\\ConstraintViolation' => $vendorDir . '/symfony/validator/ConstraintViolation.php', + 'Symfony\\Component\\Validator\\ConstraintViolationInterface' => $vendorDir . '/symfony/validator/ConstraintViolationInterface.php', + 'Symfony\\Component\\Validator\\ConstraintViolationList' => $vendorDir . '/symfony/validator/ConstraintViolationList.php', + 'Symfony\\Component\\Validator\\ConstraintViolationListInterface' => $vendorDir . '/symfony/validator/ConstraintViolationListInterface.php', + 'Symfony\\Component\\Validator\\Constraints\\AbstractComparison' => $vendorDir . '/symfony/validator/Constraints/AbstractComparison.php', + 'Symfony\\Component\\Validator\\Constraints\\AbstractComparisonValidator' => $vendorDir . '/symfony/validator/Constraints/AbstractComparisonValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\All' => $vendorDir . '/symfony/validator/Constraints/All.php', + 'Symfony\\Component\\Validator\\Constraints\\AllValidator' => $vendorDir . '/symfony/validator/Constraints/AllValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Bic' => $vendorDir . '/symfony/validator/Constraints/Bic.php', + 'Symfony\\Component\\Validator\\Constraints\\BicValidator' => $vendorDir . '/symfony/validator/Constraints/BicValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Blank' => $vendorDir . '/symfony/validator/Constraints/Blank.php', + 'Symfony\\Component\\Validator\\Constraints\\BlankValidator' => $vendorDir . '/symfony/validator/Constraints/BlankValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Callback' => $vendorDir . '/symfony/validator/Constraints/Callback.php', + 'Symfony\\Component\\Validator\\Constraints\\CallbackValidator' => $vendorDir . '/symfony/validator/Constraints/CallbackValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\CardScheme' => $vendorDir . '/symfony/validator/Constraints/CardScheme.php', + 'Symfony\\Component\\Validator\\Constraints\\CardSchemeValidator' => $vendorDir . '/symfony/validator/Constraints/CardSchemeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Choice' => $vendorDir . '/symfony/validator/Constraints/Choice.php', + 'Symfony\\Component\\Validator\\Constraints\\ChoiceValidator' => $vendorDir . '/symfony/validator/Constraints/ChoiceValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Collection' => $vendorDir . '/symfony/validator/Constraints/Collection.php', + 'Symfony\\Component\\Validator\\Constraints\\CollectionValidator' => $vendorDir . '/symfony/validator/Constraints/CollectionValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Composite' => $vendorDir . '/symfony/validator/Constraints/Composite.php', + 'Symfony\\Component\\Validator\\Constraints\\Count' => $vendorDir . '/symfony/validator/Constraints/Count.php', + 'Symfony\\Component\\Validator\\Constraints\\CountValidator' => $vendorDir . '/symfony/validator/Constraints/CountValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Country' => $vendorDir . '/symfony/validator/Constraints/Country.php', + 'Symfony\\Component\\Validator\\Constraints\\CountryValidator' => $vendorDir . '/symfony/validator/Constraints/CountryValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Currency' => $vendorDir . '/symfony/validator/Constraints/Currency.php', + 'Symfony\\Component\\Validator\\Constraints\\CurrencyValidator' => $vendorDir . '/symfony/validator/Constraints/CurrencyValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Date' => $vendorDir . '/symfony/validator/Constraints/Date.php', + 'Symfony\\Component\\Validator\\Constraints\\DateTime' => $vendorDir . '/symfony/validator/Constraints/DateTime.php', + 'Symfony\\Component\\Validator\\Constraints\\DateTimeValidator' => $vendorDir . '/symfony/validator/Constraints/DateTimeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\DateValidator' => $vendorDir . '/symfony/validator/Constraints/DateValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Email' => $vendorDir . '/symfony/validator/Constraints/Email.php', + 'Symfony\\Component\\Validator\\Constraints\\EmailValidator' => $vendorDir . '/symfony/validator/Constraints/EmailValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\EqualTo' => $vendorDir . '/symfony/validator/Constraints/EqualTo.php', + 'Symfony\\Component\\Validator\\Constraints\\EqualToValidator' => $vendorDir . '/symfony/validator/Constraints/EqualToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Existence' => $vendorDir . '/symfony/validator/Constraints/Existence.php', + 'Symfony\\Component\\Validator\\Constraints\\Expression' => $vendorDir . '/symfony/validator/Constraints/Expression.php', + 'Symfony\\Component\\Validator\\Constraints\\ExpressionValidator' => $vendorDir . '/symfony/validator/Constraints/ExpressionValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\File' => $vendorDir . '/symfony/validator/Constraints/File.php', + 'Symfony\\Component\\Validator\\Constraints\\FileValidator' => $vendorDir . '/symfony/validator/Constraints/FileValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThan' => $vendorDir . '/symfony/validator/Constraints/GreaterThan.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqual' => $vendorDir . '/symfony/validator/Constraints/GreaterThanOrEqual.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqualValidator' => $vendorDir . '/symfony/validator/Constraints/GreaterThanOrEqualValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThanValidator' => $vendorDir . '/symfony/validator/Constraints/GreaterThanValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\GroupSequence' => $vendorDir . '/symfony/validator/Constraints/GroupSequence.php', + 'Symfony\\Component\\Validator\\Constraints\\GroupSequenceProvider' => $vendorDir . '/symfony/validator/Constraints/GroupSequenceProvider.php', + 'Symfony\\Component\\Validator\\Constraints\\Iban' => $vendorDir . '/symfony/validator/Constraints/Iban.php', + 'Symfony\\Component\\Validator\\Constraints\\IbanValidator' => $vendorDir . '/symfony/validator/Constraints/IbanValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IdenticalTo' => $vendorDir . '/symfony/validator/Constraints/IdenticalTo.php', + 'Symfony\\Component\\Validator\\Constraints\\IdenticalToValidator' => $vendorDir . '/symfony/validator/Constraints/IdenticalToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Image' => $vendorDir . '/symfony/validator/Constraints/Image.php', + 'Symfony\\Component\\Validator\\Constraints\\ImageValidator' => $vendorDir . '/symfony/validator/Constraints/ImageValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Ip' => $vendorDir . '/symfony/validator/Constraints/Ip.php', + 'Symfony\\Component\\Validator\\Constraints\\IpValidator' => $vendorDir . '/symfony/validator/Constraints/IpValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IsFalse' => $vendorDir . '/symfony/validator/Constraints/IsFalse.php', + 'Symfony\\Component\\Validator\\Constraints\\IsFalseValidator' => $vendorDir . '/symfony/validator/Constraints/IsFalseValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IsNull' => $vendorDir . '/symfony/validator/Constraints/IsNull.php', + 'Symfony\\Component\\Validator\\Constraints\\IsNullValidator' => $vendorDir . '/symfony/validator/Constraints/IsNullValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IsTrue' => $vendorDir . '/symfony/validator/Constraints/IsTrue.php', + 'Symfony\\Component\\Validator\\Constraints\\IsTrueValidator' => $vendorDir . '/symfony/validator/Constraints/IsTrueValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Isbn' => $vendorDir . '/symfony/validator/Constraints/Isbn.php', + 'Symfony\\Component\\Validator\\Constraints\\IsbnValidator' => $vendorDir . '/symfony/validator/Constraints/IsbnValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Issn' => $vendorDir . '/symfony/validator/Constraints/Issn.php', + 'Symfony\\Component\\Validator\\Constraints\\IssnValidator' => $vendorDir . '/symfony/validator/Constraints/IssnValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Language' => $vendorDir . '/symfony/validator/Constraints/Language.php', + 'Symfony\\Component\\Validator\\Constraints\\LanguageValidator' => $vendorDir . '/symfony/validator/Constraints/LanguageValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Length' => $vendorDir . '/symfony/validator/Constraints/Length.php', + 'Symfony\\Component\\Validator\\Constraints\\LengthValidator' => $vendorDir . '/symfony/validator/Constraints/LengthValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThan' => $vendorDir . '/symfony/validator/Constraints/LessThan.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqual' => $vendorDir . '/symfony/validator/Constraints/LessThanOrEqual.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqualValidator' => $vendorDir . '/symfony/validator/Constraints/LessThanOrEqualValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThanValidator' => $vendorDir . '/symfony/validator/Constraints/LessThanValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Locale' => $vendorDir . '/symfony/validator/Constraints/Locale.php', + 'Symfony\\Component\\Validator\\Constraints\\LocaleValidator' => $vendorDir . '/symfony/validator/Constraints/LocaleValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Luhn' => $vendorDir . '/symfony/validator/Constraints/Luhn.php', + 'Symfony\\Component\\Validator\\Constraints\\LuhnValidator' => $vendorDir . '/symfony/validator/Constraints/LuhnValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotBlank' => $vendorDir . '/symfony/validator/Constraints/NotBlank.php', + 'Symfony\\Component\\Validator\\Constraints\\NotBlankValidator' => $vendorDir . '/symfony/validator/Constraints/NotBlankValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotEqualTo' => $vendorDir . '/symfony/validator/Constraints/NotEqualTo.php', + 'Symfony\\Component\\Validator\\Constraints\\NotEqualToValidator' => $vendorDir . '/symfony/validator/Constraints/NotEqualToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalTo' => $vendorDir . '/symfony/validator/Constraints/NotIdenticalTo.php', + 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalToValidator' => $vendorDir . '/symfony/validator/Constraints/NotIdenticalToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotNull' => $vendorDir . '/symfony/validator/Constraints/NotNull.php', + 'Symfony\\Component\\Validator\\Constraints\\NotNullValidator' => $vendorDir . '/symfony/validator/Constraints/NotNullValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Optional' => $vendorDir . '/symfony/validator/Constraints/Optional.php', + 'Symfony\\Component\\Validator\\Constraints\\Range' => $vendorDir . '/symfony/validator/Constraints/Range.php', + 'Symfony\\Component\\Validator\\Constraints\\RangeValidator' => $vendorDir . '/symfony/validator/Constraints/RangeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Regex' => $vendorDir . '/symfony/validator/Constraints/Regex.php', + 'Symfony\\Component\\Validator\\Constraints\\RegexValidator' => $vendorDir . '/symfony/validator/Constraints/RegexValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Required' => $vendorDir . '/symfony/validator/Constraints/Required.php', + 'Symfony\\Component\\Validator\\Constraints\\Time' => $vendorDir . '/symfony/validator/Constraints/Time.php', + 'Symfony\\Component\\Validator\\Constraints\\TimeValidator' => $vendorDir . '/symfony/validator/Constraints/TimeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Traverse' => $vendorDir . '/symfony/validator/Constraints/Traverse.php', + 'Symfony\\Component\\Validator\\Constraints\\Type' => $vendorDir . '/symfony/validator/Constraints/Type.php', + 'Symfony\\Component\\Validator\\Constraints\\TypeValidator' => $vendorDir . '/symfony/validator/Constraints/TypeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Url' => $vendorDir . '/symfony/validator/Constraints/Url.php', + 'Symfony\\Component\\Validator\\Constraints\\UrlValidator' => $vendorDir . '/symfony/validator/Constraints/UrlValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Uuid' => $vendorDir . '/symfony/validator/Constraints/Uuid.php', + 'Symfony\\Component\\Validator\\Constraints\\UuidValidator' => $vendorDir . '/symfony/validator/Constraints/UuidValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Valid' => $vendorDir . '/symfony/validator/Constraints/Valid.php', + 'Symfony\\Component\\Validator\\Constraints\\ValidValidator' => $vendorDir . '/symfony/validator/Constraints/ValidValidator.php', + 'Symfony\\Component\\Validator\\ContainerConstraintValidatorFactory' => $vendorDir . '/symfony/validator/ContainerConstraintValidatorFactory.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContext' => $vendorDir . '/symfony/validator/Context/ExecutionContext.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactory' => $vendorDir . '/symfony/validator/Context/ExecutionContextFactory.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactoryInterface' => $vendorDir . '/symfony/validator/Context/ExecutionContextFactoryInterface.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContextInterface' => $vendorDir . '/symfony/validator/Context/ExecutionContextInterface.php', + 'Symfony\\Component\\Validator\\DataCollector\\ValidatorDataCollector' => $vendorDir . '/symfony/validator/DataCollector/ValidatorDataCollector.php', + 'Symfony\\Component\\Validator\\DependencyInjection\\AddConstraintValidatorsPass' => $vendorDir . '/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php', + 'Symfony\\Component\\Validator\\DependencyInjection\\AddValidatorInitializersPass' => $vendorDir . '/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php', + 'Symfony\\Component\\Validator\\Exception\\BadMethodCallException' => $vendorDir . '/symfony/validator/Exception/BadMethodCallException.php', + 'Symfony\\Component\\Validator\\Exception\\ConstraintDefinitionException' => $vendorDir . '/symfony/validator/Exception/ConstraintDefinitionException.php', + 'Symfony\\Component\\Validator\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/validator/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Validator\\Exception\\GroupDefinitionException' => $vendorDir . '/symfony/validator/Exception/GroupDefinitionException.php', + 'Symfony\\Component\\Validator\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/validator/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Validator\\Exception\\InvalidOptionsException' => $vendorDir . '/symfony/validator/Exception/InvalidOptionsException.php', + 'Symfony\\Component\\Validator\\Exception\\MappingException' => $vendorDir . '/symfony/validator/Exception/MappingException.php', + 'Symfony\\Component\\Validator\\Exception\\MissingOptionsException' => $vendorDir . '/symfony/validator/Exception/MissingOptionsException.php', + 'Symfony\\Component\\Validator\\Exception\\NoSuchMetadataException' => $vendorDir . '/symfony/validator/Exception/NoSuchMetadataException.php', + 'Symfony\\Component\\Validator\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/validator/Exception/OutOfBoundsException.php', + 'Symfony\\Component\\Validator\\Exception\\RuntimeException' => $vendorDir . '/symfony/validator/Exception/RuntimeException.php', + 'Symfony\\Component\\Validator\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/validator/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\Validator\\Exception\\UnsupportedMetadataException' => $vendorDir . '/symfony/validator/Exception/UnsupportedMetadataException.php', + 'Symfony\\Component\\Validator\\Exception\\ValidatorException' => $vendorDir . '/symfony/validator/Exception/ValidatorException.php', + 'Symfony\\Component\\Validator\\GroupSequenceProviderInterface' => $vendorDir . '/symfony/validator/GroupSequenceProviderInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface' => $vendorDir . '/symfony/validator/Mapping/Cache/CacheInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Cache\\DoctrineCache' => $vendorDir . '/symfony/validator/Mapping/Cache/DoctrineCache.php', + 'Symfony\\Component\\Validator\\Mapping\\Cache\\Psr6Cache' => $vendorDir . '/symfony/validator/Mapping/Cache/Psr6Cache.php', + 'Symfony\\Component\\Validator\\Mapping\\CascadingStrategy' => $vendorDir . '/symfony/validator/Mapping/CascadingStrategy.php', + 'Symfony\\Component\\Validator\\Mapping\\ClassMetadata' => $vendorDir . '/symfony/validator/Mapping/ClassMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface' => $vendorDir . '/symfony/validator/Mapping/ClassMetadataInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Factory\\BlackHoleMetadataFactory' => $vendorDir . '/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php', + 'Symfony\\Component\\Validator\\Mapping\\Factory\\LazyLoadingMetadataFactory' => $vendorDir . '/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php', + 'Symfony\\Component\\Validator\\Mapping\\Factory\\MetadataFactoryInterface' => $vendorDir . '/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\GenericMetadata' => $vendorDir . '/symfony/validator/Mapping/GenericMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\GetterMetadata' => $vendorDir . '/symfony/validator/Mapping/GetterMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\AbstractLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/AbstractLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\AnnotationLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/AnnotationLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\FileLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/FileLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\FilesLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/FilesLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderChain' => $vendorDir . '/symfony/validator/Mapping/Loader/LoaderChain.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderInterface' => $vendorDir . '/symfony/validator/Mapping/Loader/LoaderInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\StaticMethodLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/StaticMethodLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/XmlFileLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFilesLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/XmlFilesLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFilesLoader' => $vendorDir . '/symfony/validator/Mapping/Loader/YamlFilesLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\MemberMetadata' => $vendorDir . '/symfony/validator/Mapping/MemberMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\MetadataInterface' => $vendorDir . '/symfony/validator/Mapping/MetadataInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\PropertyMetadata' => $vendorDir . '/symfony/validator/Mapping/PropertyMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\PropertyMetadataInterface' => $vendorDir . '/symfony/validator/Mapping/PropertyMetadataInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\TraversalStrategy' => $vendorDir . '/symfony/validator/Mapping/TraversalStrategy.php', + 'Symfony\\Component\\Validator\\ObjectInitializerInterface' => $vendorDir . '/symfony/validator/ObjectInitializerInterface.php', + 'Symfony\\Component\\Validator\\Test\\ConstraintValidatorTestCase' => $vendorDir . '/symfony/validator/Test/ConstraintValidatorTestCase.php', + 'Symfony\\Component\\Validator\\Test\\ConstraintViolationAssertion' => $vendorDir . '/symfony/validator/Test/ConstraintValidatorTestCase.php', + 'Symfony\\Component\\Validator\\Util\\PropertyPath' => $vendorDir . '/symfony/validator/Util/PropertyPath.php', + 'Symfony\\Component\\Validator\\Validation' => $vendorDir . '/symfony/validator/Validation.php', + 'Symfony\\Component\\Validator\\ValidatorBuilder' => $vendorDir . '/symfony/validator/ValidatorBuilder.php', + 'Symfony\\Component\\Validator\\ValidatorBuilderInterface' => $vendorDir . '/symfony/validator/ValidatorBuilderInterface.php', + 'Symfony\\Component\\Validator\\Validator\\ContextualValidatorInterface' => $vendorDir . '/symfony/validator/Validator/ContextualValidatorInterface.php', + 'Symfony\\Component\\Validator\\Validator\\RecursiveContextualValidator' => $vendorDir . '/symfony/validator/Validator/RecursiveContextualValidator.php', + 'Symfony\\Component\\Validator\\Validator\\RecursiveValidator' => $vendorDir . '/symfony/validator/Validator/RecursiveValidator.php', + 'Symfony\\Component\\Validator\\Validator\\TraceableValidator' => $vendorDir . '/symfony/validator/Validator/TraceableValidator.php', + 'Symfony\\Component\\Validator\\Validator\\ValidatorInterface' => $vendorDir . '/symfony/validator/Validator/ValidatorInterface.php', + 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilder' => $vendorDir . '/symfony/validator/Violation/ConstraintViolationBuilder.php', + 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilderInterface' => $vendorDir . '/symfony/validator/Violation/ConstraintViolationBuilderInterface.php', + 'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', + 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', + 'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php', + 'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php', + 'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/yaml/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Yaml\\Exception\\ParseException' => $vendorDir . '/symfony/yaml/Exception/ParseException.php', + 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php', + 'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php', + 'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php', + 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php', + 'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', + 'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Text_Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php', + 'Twig\\Cache\\CacheInterface' => $vendorDir . '/twig/twig/src/Cache/CacheInterface.php', + 'Twig\\Cache\\FilesystemCache' => $vendorDir . '/twig/twig/src/Cache/FilesystemCache.php', + 'Twig\\Cache\\NullCache' => $vendorDir . '/twig/twig/src/Cache/NullCache.php', + 'Twig\\Compiler' => $vendorDir . '/twig/twig/src/Compiler.php', + 'Twig\\Environment' => $vendorDir . '/twig/twig/src/Environment.php', + 'Twig\\Error\\Error' => $vendorDir . '/twig/twig/src/Error/Error.php', + 'Twig\\Error\\LoaderError' => $vendorDir . '/twig/twig/src/Error/LoaderError.php', + 'Twig\\Error\\RuntimeError' => $vendorDir . '/twig/twig/src/Error/RuntimeError.php', + 'Twig\\Error\\SyntaxError' => $vendorDir . '/twig/twig/src/Error/SyntaxError.php', + 'Twig\\ExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser.php', + 'Twig\\ExtensionSet' => $vendorDir . '/twig/twig/src/ExtensionSet.php', + 'Twig\\Extension\\AbstractExtension' => $vendorDir . '/twig/twig/src/Extension/AbstractExtension.php', + 'Twig\\Extension\\CoreExtension' => $vendorDir . '/twig/twig/src/Extension/CoreExtension.php', + 'Twig\\Extension\\DebugExtension' => $vendorDir . '/twig/twig/src/Extension/DebugExtension.php', + 'Twig\\Extension\\EscaperExtension' => $vendorDir . '/twig/twig/src/Extension/EscaperExtension.php', + 'Twig\\Extension\\ExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/ExtensionInterface.php', + 'Twig\\Extension\\GlobalsInterface' => $vendorDir . '/twig/twig/src/Extension/GlobalsInterface.php', + 'Twig\\Extension\\InitRuntimeInterface' => $vendorDir . '/twig/twig/src/Extension/InitRuntimeInterface.php', + 'Twig\\Extension\\OptimizerExtension' => $vendorDir . '/twig/twig/src/Extension/OptimizerExtension.php', + 'Twig\\Extension\\ProfilerExtension' => $vendorDir . '/twig/twig/src/Extension/ProfilerExtension.php', + 'Twig\\Extension\\RuntimeExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', + 'Twig\\Extension\\SandboxExtension' => $vendorDir . '/twig/twig/src/Extension/SandboxExtension.php', + 'Twig\\Extension\\StagingExtension' => $vendorDir . '/twig/twig/src/Extension/StagingExtension.php', + 'Twig\\Extension\\StringLoaderExtension' => $vendorDir . '/twig/twig/src/Extension/StringLoaderExtension.php', + 'Twig\\FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/src/FileExtensionEscapingStrategy.php', + 'Twig\\Lexer' => $vendorDir . '/twig/twig/src/Lexer.php', + 'Twig\\Loader\\ArrayLoader' => $vendorDir . '/twig/twig/src/Loader/ArrayLoader.php', + 'Twig\\Loader\\ChainLoader' => $vendorDir . '/twig/twig/src/Loader/ChainLoader.php', + 'Twig\\Loader\\ExistsLoaderInterface' => $vendorDir . '/twig/twig/src/Loader/ExistsLoaderInterface.php', + 'Twig\\Loader\\FilesystemLoader' => $vendorDir . '/twig/twig/src/Loader/FilesystemLoader.php', + 'Twig\\Loader\\LoaderInterface' => $vendorDir . '/twig/twig/src/Loader/LoaderInterface.php', + 'Twig\\Loader\\SourceContextLoaderInterface' => $vendorDir . '/twig/twig/src/Loader/SourceContextLoaderInterface.php', + 'Twig\\Markup' => $vendorDir . '/twig/twig/src/Markup.php', + 'Twig\\NodeTraverser' => $vendorDir . '/twig/twig/src/NodeTraverser.php', + 'Twig\\NodeVisitor\\AbstractNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', + 'Twig\\NodeVisitor\\EscaperNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\NodeVisitorInterface' => $vendorDir . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', + 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', + 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', + 'Twig\\NodeVisitor\\SandboxNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php', + 'Twig\\Node\\AutoEscapeNode' => $vendorDir . '/twig/twig/src/Node/AutoEscapeNode.php', + 'Twig\\Node\\BlockNode' => $vendorDir . '/twig/twig/src/Node/BlockNode.php', + 'Twig\\Node\\BlockReferenceNode' => $vendorDir . '/twig/twig/src/Node/BlockReferenceNode.php', + 'Twig\\Node\\BodyNode' => $vendorDir . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityNode.php', + 'Twig\\Node\\DoNode' => $vendorDir . '/twig/twig/src/Node/DoNode.php', + 'Twig\\Node\\EmbedNode' => $vendorDir . '/twig/twig/src/Node/EmbedNode.php', + 'Twig\\Node\\Expression\\AbstractExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AbstractExpression.php', + 'Twig\\Node\\Expression\\ArrayExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ArrayExpression.php', + 'Twig\\Node\\Expression\\AssignNameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AssignNameExpression.php', + 'Twig\\Node\\Expression\\Binary\\AbstractBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AbstractBinary.php', + 'Twig\\Node\\Expression\\Binary\\AddBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AddBinary.php', + 'Twig\\Node\\Expression\\Binary\\AndBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseAndBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseOrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseXorBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php', + 'Twig\\Node\\Expression\\Binary\\ConcatBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ConcatBinary.php', + 'Twig\\Node\\Expression\\Binary\\DivBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/DivBinary.php', + 'Twig\\Node\\Expression\\Binary\\EndsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\EqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/EqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\FloorDivBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/GreaterBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\InBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/InBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/LessBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\MatchesBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MatchesBinary.php', + 'Twig\\Node\\Expression\\Binary\\ModBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ModBinary.php', + 'Twig\\Node\\Expression\\Binary\\MulBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MulBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotInBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php', + 'Twig\\Node\\Expression\\Binary\\OrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', + 'Twig\\Node\\Expression\\Binary\\PowerBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', + 'Twig\\Node\\Expression\\Binary\\RangeBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\SubBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', + 'Twig\\Node\\Expression\\BlockReferenceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', + 'Twig\\Node\\Expression\\CallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/CallExpression.php', + 'Twig\\Node\\Expression\\ConditionalExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConditionalExpression.php', + 'Twig\\Node\\Expression\\ConstantExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConstantExpression.php', + 'Twig\\Node\\Expression\\FilterExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FilterExpression.php', + 'Twig\\Node\\Expression\\Filter\\DefaultFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php', + 'Twig\\Node\\Expression\\FunctionExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FunctionExpression.php', + 'Twig\\Node\\Expression\\GetAttrExpression' => $vendorDir . '/twig/twig/src/Node/Expression/GetAttrExpression.php', + 'Twig\\Node\\Expression\\MethodCallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/MethodCallExpression.php', + 'Twig\\Node\\Expression\\NameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NameExpression.php', + 'Twig\\Node\\Expression\\NullCoalesceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php', + 'Twig\\Node\\Expression\\ParentExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ParentExpression.php', + 'Twig\\Node\\Expression\\TempNameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/TempNameExpression.php', + 'Twig\\Node\\Expression\\TestExpression' => $vendorDir . '/twig/twig/src/Node/Expression/TestExpression.php', + 'Twig\\Node\\Expression\\Test\\ConstantTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/ConstantTest.php', + 'Twig\\Node\\Expression\\Test\\DefinedTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/DefinedTest.php', + 'Twig\\Node\\Expression\\Test\\DivisiblebyTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php', + 'Twig\\Node\\Expression\\Test\\EvenTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/EvenTest.php', + 'Twig\\Node\\Expression\\Test\\NullTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/NullTest.php', + 'Twig\\Node\\Expression\\Test\\OddTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/OddTest.php', + 'Twig\\Node\\Expression\\Test\\SameasTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/SameasTest.php', + 'Twig\\Node\\Expression\\Unary\\AbstractUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/AbstractUnary.php', + 'Twig\\Node\\Expression\\Unary\\NegUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', + 'Twig\\Node\\Expression\\Unary\\NotUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', + 'Twig\\Node\\Expression\\Unary\\PosUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\FlushNode' => $vendorDir . '/twig/twig/src/Node/FlushNode.php', + 'Twig\\Node\\ForLoopNode' => $vendorDir . '/twig/twig/src/Node/ForLoopNode.php', + 'Twig\\Node\\ForNode' => $vendorDir . '/twig/twig/src/Node/ForNode.php', + 'Twig\\Node\\IfNode' => $vendorDir . '/twig/twig/src/Node/IfNode.php', + 'Twig\\Node\\ImportNode' => $vendorDir . '/twig/twig/src/Node/ImportNode.php', + 'Twig\\Node\\IncludeNode' => $vendorDir . '/twig/twig/src/Node/IncludeNode.php', + 'Twig\\Node\\MacroNode' => $vendorDir . '/twig/twig/src/Node/MacroNode.php', + 'Twig\\Node\\ModuleNode' => $vendorDir . '/twig/twig/src/Node/ModuleNode.php', + 'Twig\\Node\\Node' => $vendorDir . '/twig/twig/src/Node/Node.php', + 'Twig\\Node\\NodeCaptureInterface' => $vendorDir . '/twig/twig/src/Node/NodeCaptureInterface.php', + 'Twig\\Node\\NodeOutputInterface' => $vendorDir . '/twig/twig/src/Node/NodeOutputInterface.php', + 'Twig\\Node\\PrintNode' => $vendorDir . '/twig/twig/src/Node/PrintNode.php', + 'Twig\\Node\\SandboxNode' => $vendorDir . '/twig/twig/src/Node/SandboxNode.php', + 'Twig\\Node\\SandboxedPrintNode' => $vendorDir . '/twig/twig/src/Node/SandboxedPrintNode.php', + 'Twig\\Node\\SetNode' => $vendorDir . '/twig/twig/src/Node/SetNode.php', + 'Twig\\Node\\SpacelessNode' => $vendorDir . '/twig/twig/src/Node/SpacelessNode.php', + 'Twig\\Node\\TextNode' => $vendorDir . '/twig/twig/src/Node/TextNode.php', + 'Twig\\Node\\WithNode' => $vendorDir . '/twig/twig/src/Node/WithNode.php', + 'Twig\\Parser' => $vendorDir . '/twig/twig/src/Parser.php', + 'Twig\\Profiler\\Dumper\\BaseDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BaseDumper.php', + 'Twig\\Profiler\\Dumper\\BlackfireDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php', + 'Twig\\Profiler\\Dumper\\HtmlDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/HtmlDumper.php', + 'Twig\\Profiler\\Dumper\\TextDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/TextDumper.php', + 'Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor' => $vendorDir . '/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php', + 'Twig\\Profiler\\Node\\EnterProfileNode' => $vendorDir . '/twig/twig/src/Profiler/Node/EnterProfileNode.php', + 'Twig\\Profiler\\Node\\LeaveProfileNode' => $vendorDir . '/twig/twig/src/Profiler/Node/LeaveProfileNode.php', + 'Twig\\Profiler\\Profile' => $vendorDir . '/twig/twig/src/Profiler/Profile.php', + 'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php', + 'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php', + 'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => $vendorDir . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php', + 'Twig\\Sandbox\\SecurityError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig\\Sandbox\\SecurityNotAllowedMethodError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig\\Sandbox\\SecurityNotAllowedPropertyError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig\\Sandbox\\SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php', + 'Twig\\Sandbox\\SecurityPolicy' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicy.php', + 'Twig\\Sandbox\\SecurityPolicyInterface' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php', + 'Twig\\Source' => $vendorDir . '/twig/twig/src/Source.php', + 'Twig\\Template' => $vendorDir . '/twig/twig/src/Template.php', + 'Twig\\TemplateWrapper' => $vendorDir . '/twig/twig/src/TemplateWrapper.php', + 'Twig\\Test\\IntegrationTestCase' => $vendorDir . '/twig/twig/src/Test/IntegrationTestCase.php', + 'Twig\\Test\\NodeTestCase' => $vendorDir . '/twig/twig/src/Test/NodeTestCase.php', + 'Twig\\Token' => $vendorDir . '/twig/twig/src/Token.php', + 'Twig\\TokenParser\\AbstractTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AbstractTokenParser.php', + 'Twig\\TokenParser\\AutoEscapeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AutoEscapeTokenParser.php', + 'Twig\\TokenParser\\BlockTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/BlockTokenParser.php', + 'Twig\\TokenParser\\DoTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/DoTokenParser.php', + 'Twig\\TokenParser\\EmbedTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/EmbedTokenParser.php', + 'Twig\\TokenParser\\ExtendsTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', + 'Twig\\TokenParser\\FilterTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FilterTokenParser.php', + 'Twig\\TokenParser\\FlushTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FlushTokenParser.php', + 'Twig\\TokenParser\\ForTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ForTokenParser.php', + 'Twig\\TokenParser\\FromTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FromTokenParser.php', + 'Twig\\TokenParser\\IfTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IfTokenParser.php', + 'Twig\\TokenParser\\ImportTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ImportTokenParser.php', + 'Twig\\TokenParser\\IncludeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IncludeTokenParser.php', + 'Twig\\TokenParser\\MacroTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/MacroTokenParser.php', + 'Twig\\TokenParser\\SandboxTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SandboxTokenParser.php', + 'Twig\\TokenParser\\SetTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SetTokenParser.php', + 'Twig\\TokenParser\\SpacelessTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SpacelessTokenParser.php', + 'Twig\\TokenParser\\TokenParserInterface' => $vendorDir . '/twig/twig/src/TokenParser/TokenParserInterface.php', + 'Twig\\TokenParser\\UseTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/UseTokenParser.php', + 'Twig\\TokenParser\\WithTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/WithTokenParser.php', + 'Twig\\TokenStream' => $vendorDir . '/twig/twig/src/TokenStream.php', + 'Twig\\TwigFilter' => $vendorDir . '/twig/twig/src/TwigFilter.php', + 'Twig\\TwigFunction' => $vendorDir . '/twig/twig/src/TwigFunction.php', + 'Twig\\TwigTest' => $vendorDir . '/twig/twig/src/TwigTest.php', + 'Twig\\Util\\DeprecationCollector' => $vendorDir . '/twig/twig/src/Util/DeprecationCollector.php', + 'Twig\\Util\\TemplateDirIterator' => $vendorDir . '/twig/twig/src/Util/TemplateDirIterator.php', + 'Twig_BaseNodeVisitor' => $vendorDir . '/twig/twig/lib/Twig/BaseNodeVisitor.php', + 'Twig_CacheInterface' => $vendorDir . '/twig/twig/lib/Twig/CacheInterface.php', + 'Twig_Cache_Filesystem' => $vendorDir . '/twig/twig/lib/Twig/Cache/Filesystem.php', + 'Twig_Cache_Null' => $vendorDir . '/twig/twig/lib/Twig/Cache/Null.php', + 'Twig_Compiler' => $vendorDir . '/twig/twig/lib/Twig/Compiler.php', + 'Twig_ContainerRuntimeLoader' => $vendorDir . '/twig/twig/lib/Twig/ContainerRuntimeLoader.php', + 'Twig_Environment' => $vendorDir . '/twig/twig/lib/Twig/Environment.php', + 'Twig_Error' => $vendorDir . '/twig/twig/lib/Twig/Error.php', + 'Twig_Error_Loader' => $vendorDir . '/twig/twig/lib/Twig/Error/Loader.php', + 'Twig_Error_Runtime' => $vendorDir . '/twig/twig/lib/Twig/Error/Runtime.php', + 'Twig_Error_Syntax' => $vendorDir . '/twig/twig/lib/Twig/Error/Syntax.php', + 'Twig_ExistsLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/ExistsLoaderInterface.php', + 'Twig_ExpressionParser' => $vendorDir . '/twig/twig/lib/Twig/ExpressionParser.php', + 'Twig_Extension' => $vendorDir . '/twig/twig/lib/Twig/Extension.php', + 'Twig_ExtensionInterface' => $vendorDir . '/twig/twig/lib/Twig/ExtensionInterface.php', + 'Twig_ExtensionSet' => $vendorDir . '/twig/twig/lib/Twig/ExtensionSet.php', + 'Twig_Extension_Core' => $vendorDir . '/twig/twig/lib/Twig/Extension/Core.php', + 'Twig_Extension_Debug' => $vendorDir . '/twig/twig/lib/Twig/Extension/Debug.php', + 'Twig_Extension_Escaper' => $vendorDir . '/twig/twig/lib/Twig/Extension/Escaper.php', + 'Twig_Extension_GlobalsInterface' => $vendorDir . '/twig/twig/lib/Twig/Extension/GlobalsInterface.php', + 'Twig_Extension_InitRuntimeInterface' => $vendorDir . '/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php', + 'Twig_Extension_Optimizer' => $vendorDir . '/twig/twig/lib/Twig/Extension/Optimizer.php', + 'Twig_Extension_Profiler' => $vendorDir . '/twig/twig/lib/Twig/Extension/Profiler.php', + 'Twig_Extension_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/Extension/Sandbox.php', + 'Twig_Extension_Staging' => $vendorDir . '/twig/twig/lib/Twig/Extension/Staging.php', + 'Twig_Extension_StringLoader' => $vendorDir . '/twig/twig/lib/Twig/Extension/StringLoader.php', + 'Twig_FactoryRuntimeLoader' => $vendorDir . '/twig/twig/lib/Twig/FactoryRuntimeLoader.php', + 'Twig_FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php', + 'Twig_Filter' => $vendorDir . '/twig/twig/lib/Twig/Filter.php', + 'Twig_Function' => $vendorDir . '/twig/twig/lib/Twig/Function.php', + 'Twig_Lexer' => $vendorDir . '/twig/twig/lib/Twig/Lexer.php', + 'Twig_LoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/LoaderInterface.php', + 'Twig_Loader_Array' => $vendorDir . '/twig/twig/lib/Twig/Loader/Array.php', + 'Twig_Loader_Chain' => $vendorDir . '/twig/twig/lib/Twig/Loader/Chain.php', + 'Twig_Loader_Filesystem' => $vendorDir . '/twig/twig/lib/Twig/Loader/Filesystem.php', + 'Twig_Markup' => $vendorDir . '/twig/twig/lib/Twig/Markup.php', + 'Twig_Node' => $vendorDir . '/twig/twig/lib/Twig/Node.php', + 'Twig_NodeCaptureInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeCaptureInterface.php', + 'Twig_NodeOutputInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeOutputInterface.php', + 'Twig_NodeTraverser' => $vendorDir . '/twig/twig/lib/Twig/NodeTraverser.php', + 'Twig_NodeVisitorInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitorInterface.php', + 'Twig_NodeVisitor_Escaper' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Escaper.php', + 'Twig_NodeVisitor_Optimizer' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Optimizer.php', + 'Twig_NodeVisitor_SafeAnalysis' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php', + 'Twig_NodeVisitor_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Sandbox.php', + 'Twig_Node_AutoEscape' => $vendorDir . '/twig/twig/lib/Twig/Node/AutoEscape.php', + 'Twig_Node_Block' => $vendorDir . '/twig/twig/lib/Twig/Node/Block.php', + 'Twig_Node_BlockReference' => $vendorDir . '/twig/twig/lib/Twig/Node/BlockReference.php', + 'Twig_Node_Body' => $vendorDir . '/twig/twig/lib/Twig/Node/Body.php', + 'Twig_Node_CheckSecurity' => $vendorDir . '/twig/twig/lib/Twig/Node/CheckSecurity.php', + 'Twig_Node_Do' => $vendorDir . '/twig/twig/lib/Twig/Node/Do.php', + 'Twig_Node_Embed' => $vendorDir . '/twig/twig/lib/Twig/Node/Embed.php', + 'Twig_Node_Expression' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression.php', + 'Twig_Node_Expression_Array' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Array.php', + 'Twig_Node_Expression_AssignName' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/AssignName.php', + 'Twig_Node_Expression_Binary' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary.php', + 'Twig_Node_Expression_Binary_Add' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Add.php', + 'Twig_Node_Expression_Binary_And' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/And.php', + 'Twig_Node_Expression_Binary_BitwiseAnd' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php', + 'Twig_Node_Expression_Binary_BitwiseOr' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php', + 'Twig_Node_Expression_Binary_BitwiseXor' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php', + 'Twig_Node_Expression_Binary_Concat' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php', + 'Twig_Node_Expression_Binary_Div' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Div.php', + 'Twig_Node_Expression_Binary_EndsWith' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php', + 'Twig_Node_Expression_Binary_Equal' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php', + 'Twig_Node_Expression_Binary_FloorDiv' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php', + 'Twig_Node_Expression_Binary_Greater' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php', + 'Twig_Node_Expression_Binary_GreaterEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php', + 'Twig_Node_Expression_Binary_In' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/In.php', + 'Twig_Node_Expression_Binary_Less' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Less.php', + 'Twig_Node_Expression_Binary_LessEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php', + 'Twig_Node_Expression_Binary_Matches' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php', + 'Twig_Node_Expression_Binary_Mod' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php', + 'Twig_Node_Expression_Binary_Mul' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php', + 'Twig_Node_Expression_Binary_NotEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php', + 'Twig_Node_Expression_Binary_NotIn' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php', + 'Twig_Node_Expression_Binary_Or' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Or.php', + 'Twig_Node_Expression_Binary_Power' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Power.php', + 'Twig_Node_Expression_Binary_Range' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Range.php', + 'Twig_Node_Expression_Binary_StartsWith' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php', + 'Twig_Node_Expression_Binary_Sub' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php', + 'Twig_Node_Expression_BlockReference' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/BlockReference.php', + 'Twig_Node_Expression_Call' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Call.php', + 'Twig_Node_Expression_Conditional' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Conditional.php', + 'Twig_Node_Expression_Constant' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Constant.php', + 'Twig_Node_Expression_Filter' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Filter.php', + 'Twig_Node_Expression_Filter_Default' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Filter/Default.php', + 'Twig_Node_Expression_Function' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Function.php', + 'Twig_Node_Expression_GetAttr' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/GetAttr.php', + 'Twig_Node_Expression_MethodCall' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/MethodCall.php', + 'Twig_Node_Expression_Name' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Name.php', + 'Twig_Node_Expression_NullCoalesce' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php', + 'Twig_Node_Expression_Parent' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Parent.php', + 'Twig_Node_Expression_TempName' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/TempName.php', + 'Twig_Node_Expression_Test' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test.php', + 'Twig_Node_Expression_Test_Constant' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Constant.php', + 'Twig_Node_Expression_Test_Defined' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Defined.php', + 'Twig_Node_Expression_Test_Divisibleby' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php', + 'Twig_Node_Expression_Test_Even' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Even.php', + 'Twig_Node_Expression_Test_Null' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Null.php', + 'Twig_Node_Expression_Test_Odd' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Odd.php', + 'Twig_Node_Expression_Test_Sameas' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php', + 'Twig_Node_Expression_Unary' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary.php', + 'Twig_Node_Expression_Unary_Neg' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php', + 'Twig_Node_Expression_Unary_Not' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Not.php', + 'Twig_Node_Expression_Unary_Pos' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php', + 'Twig_Node_Flush' => $vendorDir . '/twig/twig/lib/Twig/Node/Flush.php', + 'Twig_Node_For' => $vendorDir . '/twig/twig/lib/Twig/Node/For.php', + 'Twig_Node_ForLoop' => $vendorDir . '/twig/twig/lib/Twig/Node/ForLoop.php', + 'Twig_Node_If' => $vendorDir . '/twig/twig/lib/Twig/Node/If.php', + 'Twig_Node_Import' => $vendorDir . '/twig/twig/lib/Twig/Node/Import.php', + 'Twig_Node_Include' => $vendorDir . '/twig/twig/lib/Twig/Node/Include.php', + 'Twig_Node_Macro' => $vendorDir . '/twig/twig/lib/Twig/Node/Macro.php', + 'Twig_Node_Module' => $vendorDir . '/twig/twig/lib/Twig/Node/Module.php', + 'Twig_Node_Print' => $vendorDir . '/twig/twig/lib/Twig/Node/Print.php', + 'Twig_Node_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/Node/Sandbox.php', + 'Twig_Node_SandboxedPrint' => $vendorDir . '/twig/twig/lib/Twig/Node/SandboxedPrint.php', + 'Twig_Node_Set' => $vendorDir . '/twig/twig/lib/Twig/Node/Set.php', + 'Twig_Node_Spaceless' => $vendorDir . '/twig/twig/lib/Twig/Node/Spaceless.php', + 'Twig_Node_Text' => $vendorDir . '/twig/twig/lib/Twig/Node/Text.php', + 'Twig_Node_With' => $vendorDir . '/twig/twig/lib/Twig/Node/With.php', + 'Twig_Parser' => $vendorDir . '/twig/twig/lib/Twig/Parser.php', + 'Twig_Profiler_Dumper_Base' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Base.php', + 'Twig_Profiler_Dumper_Blackfire' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php', + 'Twig_Profiler_Dumper_Html' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Html.php', + 'Twig_Profiler_Dumper_Text' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Text.php', + 'Twig_Profiler_NodeVisitor_Profiler' => $vendorDir . '/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php', + 'Twig_Profiler_Node_EnterProfile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php', + 'Twig_Profiler_Node_LeaveProfile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php', + 'Twig_Profiler_Profile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Profile.php', + 'Twig_RuntimeLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/RuntimeLoaderInterface.php', + 'Twig_Sandbox_SecurityError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityError.php', + 'Twig_Sandbox_SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig_Sandbox_SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig_Sandbox_SecurityNotAllowedMethodError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig_Sandbox_SecurityNotAllowedPropertyError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig_Sandbox_SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php', + 'Twig_Sandbox_SecurityPolicy' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php', + 'Twig_Sandbox_SecurityPolicyInterface' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php', + 'Twig_SimpleFilter' => $vendorDir . '/twig/twig/lib/Twig/SimpleFilter.php', + 'Twig_SimpleFunction' => $vendorDir . '/twig/twig/lib/Twig/SimpleFunction.php', + 'Twig_SimpleTest' => $vendorDir . '/twig/twig/lib/Twig/SimpleTest.php', + 'Twig_Source' => $vendorDir . '/twig/twig/lib/Twig/Source.php', + 'Twig_SourceContextLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/SourceContextLoaderInterface.php', + 'Twig_Template' => $vendorDir . '/twig/twig/lib/Twig/Template.php', + 'Twig_TemplateWrapper' => $vendorDir . '/twig/twig/lib/Twig/TemplateWrapper.php', + 'Twig_Test' => $vendorDir . '/twig/twig/lib/Twig/Test.php', + 'Twig_Test_IntegrationTestCase' => $vendorDir . '/twig/twig/lib/Twig/Test/IntegrationTestCase.php', + 'Twig_Test_NodeTestCase' => $vendorDir . '/twig/twig/lib/Twig/Test/NodeTestCase.php', + 'Twig_Token' => $vendorDir . '/twig/twig/lib/Twig/Token.php', + 'Twig_TokenParser' => $vendorDir . '/twig/twig/lib/Twig/TokenParser.php', + 'Twig_TokenParserInterface' => $vendorDir . '/twig/twig/lib/Twig/TokenParserInterface.php', + 'Twig_TokenParser_AutoEscape' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/AutoEscape.php', + 'Twig_TokenParser_Block' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Block.php', + 'Twig_TokenParser_Do' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Do.php', + 'Twig_TokenParser_Embed' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Embed.php', + 'Twig_TokenParser_Extends' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Extends.php', + 'Twig_TokenParser_Filter' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Filter.php', + 'Twig_TokenParser_Flush' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Flush.php', + 'Twig_TokenParser_For' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/For.php', + 'Twig_TokenParser_From' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/From.php', + 'Twig_TokenParser_If' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/If.php', + 'Twig_TokenParser_Import' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Import.php', + 'Twig_TokenParser_Include' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Include.php', + 'Twig_TokenParser_Macro' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Macro.php', + 'Twig_TokenParser_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Sandbox.php', + 'Twig_TokenParser_Set' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Set.php', + 'Twig_TokenParser_Spaceless' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Spaceless.php', + 'Twig_TokenParser_Use' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Use.php', + 'Twig_TokenParser_With' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/With.php', + 'Twig_TokenStream' => $vendorDir . '/twig/twig/lib/Twig/TokenStream.php', + 'Twig_Util_DeprecationCollector' => $vendorDir . '/twig/twig/lib/Twig/Util/DeprecationCollector.php', + 'Twig_Util_TemplateDirIterator' => $vendorDir . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php', + 'Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php', + 'WellingGuzman\\OAuth2\\Client\\Provider\\Exception\\OktaIdentityProviderException' => $vendorDir . '/wellingguzman/oauth2-okta/src/Provider/Exception/OktaIdentityProviderException.php', + 'WellingGuzman\\OAuth2\\Client\\Provider\\Okta' => $vendorDir . '/wellingguzman/oauth2-okta/src/Provider/Okta.php', + 'WellingGuzman\\OAuth2\\Client\\Provider\\OktaResourceOwner' => $vendorDir . '/wellingguzman/oauth2-okta/src/Provider/OktaResourceOwner.php', + 'Zend\\Db\\Adapter\\Adapter' => $vendorDir . '/zendframework/zend-db/src/Adapter/Adapter.php', + 'Zend\\Db\\Adapter\\AdapterAbstractServiceFactory' => $vendorDir . '/zendframework/zend-db/src/Adapter/AdapterAbstractServiceFactory.php', + 'Zend\\Db\\Adapter\\AdapterAwareInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/AdapterAwareInterface.php', + 'Zend\\Db\\Adapter\\AdapterAwareTrait' => $vendorDir . '/zendframework/zend-db/src/Adapter/AdapterAwareTrait.php', + 'Zend\\Db\\Adapter\\AdapterInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/AdapterInterface.php', + 'Zend\\Db\\Adapter\\AdapterServiceFactory' => $vendorDir . '/zendframework/zend-db/src/Adapter/AdapterServiceFactory.php', + 'Zend\\Db\\Adapter\\Driver\\AbstractConnection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/AbstractConnection.php', + 'Zend\\Db\\Adapter\\Driver\\ConnectionInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/ConnectionInterface.php', + 'Zend\\Db\\Adapter\\Driver\\DriverInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/DriverInterface.php', + 'Zend\\Db\\Adapter\\Driver\\Feature\\AbstractFeature' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Feature/AbstractFeature.php', + 'Zend\\Db\\Adapter\\Driver\\Feature\\DriverFeatureInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Feature/DriverFeatureInterface.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\Connection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\IbmDb2' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/IbmDb2.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\Result' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/Result.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\Statement' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Connection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Mysqli' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Mysqli.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Result' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Statement' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Connection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Feature\\RowCounter' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Feature/RowCounter.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Oci8' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Oci8.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Result' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Statement' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Connection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Feature\\OracleRowCounter' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Feature/OracleRowCounter.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Feature\\SqliteRowCounter' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Feature/SqliteRowCounter.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Pdo' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Pdo.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Result' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Statement' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Connection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Pgsql' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Pgsql.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Result' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Statement' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\ResultInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/ResultInterface.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Connection' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Exception\\ErrorException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Exception/ErrorException.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Exception/ExceptionInterface.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Result' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Sqlsrv' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Sqlsrv.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Statement' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\StatementInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Driver/StatementInterface.php', + 'Zend\\Db\\Adapter\\Exception\\ErrorException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/ErrorException.php', + 'Zend\\Db\\Adapter\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/ExceptionInterface.php', + 'Zend\\Db\\Adapter\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/InvalidArgumentException.php', + 'Zend\\Db\\Adapter\\Exception\\InvalidConnectionParametersException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/InvalidConnectionParametersException.php', + 'Zend\\Db\\Adapter\\Exception\\InvalidQueryException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/InvalidQueryException.php', + 'Zend\\Db\\Adapter\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/RuntimeException.php', + 'Zend\\Db\\Adapter\\Exception\\UnexpectedValueException' => $vendorDir . '/zendframework/zend-db/src/Adapter/Exception/UnexpectedValueException.php', + 'Zend\\Db\\Adapter\\ParameterContainer' => $vendorDir . '/zendframework/zend-db/src/Adapter/ParameterContainer.php', + 'Zend\\Db\\Adapter\\Platform\\AbstractPlatform' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/AbstractPlatform.php', + 'Zend\\Db\\Adapter\\Platform\\IbmDb2' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/IbmDb2.php', + 'Zend\\Db\\Adapter\\Platform\\Mysql' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/Mysql.php', + 'Zend\\Db\\Adapter\\Platform\\Oracle' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/Oracle.php', + 'Zend\\Db\\Adapter\\Platform\\PlatformInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/PlatformInterface.php', + 'Zend\\Db\\Adapter\\Platform\\Postgresql' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/Postgresql.php', + 'Zend\\Db\\Adapter\\Platform\\Sql92' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/Sql92.php', + 'Zend\\Db\\Adapter\\Platform\\SqlServer' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/SqlServer.php', + 'Zend\\Db\\Adapter\\Platform\\Sqlite' => $vendorDir . '/zendframework/zend-db/src/Adapter/Platform/Sqlite.php', + 'Zend\\Db\\Adapter\\Profiler\\Profiler' => $vendorDir . '/zendframework/zend-db/src/Adapter/Profiler/Profiler.php', + 'Zend\\Db\\Adapter\\Profiler\\ProfilerAwareInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Profiler/ProfilerAwareInterface.php', + 'Zend\\Db\\Adapter\\Profiler\\ProfilerInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/Profiler/ProfilerInterface.php', + 'Zend\\Db\\Adapter\\StatementContainer' => $vendorDir . '/zendframework/zend-db/src/Adapter/StatementContainer.php', + 'Zend\\Db\\Adapter\\StatementContainerInterface' => $vendorDir . '/zendframework/zend-db/src/Adapter/StatementContainerInterface.php', + 'Zend\\Db\\ConfigProvider' => $vendorDir . '/zendframework/zend-db/src/ConfigProvider.php', + 'Zend\\Db\\Exception\\ErrorException' => $vendorDir . '/zendframework/zend-db/src/Exception/ErrorException.php', + 'Zend\\Db\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/Exception/ExceptionInterface.php', + 'Zend\\Db\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-db/src/Exception/InvalidArgumentException.php', + 'Zend\\Db\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-db/src/Exception/RuntimeException.php', + 'Zend\\Db\\Exception\\UnexpectedValueException' => $vendorDir . '/zendframework/zend-db/src/Exception/UnexpectedValueException.php', + 'Zend\\Db\\Metadata\\Metadata' => $vendorDir . '/zendframework/zend-db/src/Metadata/Metadata.php', + 'Zend\\Db\\Metadata\\MetadataInterface' => $vendorDir . '/zendframework/zend-db/src/Metadata/MetadataInterface.php', + 'Zend\\Db\\Metadata\\Object\\AbstractTableObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/AbstractTableObject.php', + 'Zend\\Db\\Metadata\\Object\\ColumnObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/ColumnObject.php', + 'Zend\\Db\\Metadata\\Object\\ConstraintKeyObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/ConstraintKeyObject.php', + 'Zend\\Db\\Metadata\\Object\\ConstraintObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/ConstraintObject.php', + 'Zend\\Db\\Metadata\\Object\\TableObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/TableObject.php', + 'Zend\\Db\\Metadata\\Object\\TriggerObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/TriggerObject.php', + 'Zend\\Db\\Metadata\\Object\\ViewObject' => $vendorDir . '/zendframework/zend-db/src/Metadata/Object/ViewObject.php', + 'Zend\\Db\\Metadata\\Source\\AbstractSource' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/AbstractSource.php', + 'Zend\\Db\\Metadata\\Source\\Factory' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/Factory.php', + 'Zend\\Db\\Metadata\\Source\\MysqlMetadata' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/MysqlMetadata.php', + 'Zend\\Db\\Metadata\\Source\\OracleMetadata' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/OracleMetadata.php', + 'Zend\\Db\\Metadata\\Source\\PostgresqlMetadata' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/PostgresqlMetadata.php', + 'Zend\\Db\\Metadata\\Source\\SqlServerMetadata' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/SqlServerMetadata.php', + 'Zend\\Db\\Metadata\\Source\\SqliteMetadata' => $vendorDir . '/zendframework/zend-db/src/Metadata/Source/SqliteMetadata.php', + 'Zend\\Db\\Module' => $vendorDir . '/zendframework/zend-db/src/Module.php', + 'Zend\\Db\\ResultSet\\AbstractResultSet' => $vendorDir . '/zendframework/zend-db/src/ResultSet/AbstractResultSet.php', + 'Zend\\Db\\ResultSet\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/ResultSet/Exception/ExceptionInterface.php', + 'Zend\\Db\\ResultSet\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-db/src/ResultSet/Exception/InvalidArgumentException.php', + 'Zend\\Db\\ResultSet\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-db/src/ResultSet/Exception/RuntimeException.php', + 'Zend\\Db\\ResultSet\\HydratingResultSet' => $vendorDir . '/zendframework/zend-db/src/ResultSet/HydratingResultSet.php', + 'Zend\\Db\\ResultSet\\ResultSet' => $vendorDir . '/zendframework/zend-db/src/ResultSet/ResultSet.php', + 'Zend\\Db\\ResultSet\\ResultSetInterface' => $vendorDir . '/zendframework/zend-db/src/ResultSet/ResultSetInterface.php', + 'Zend\\Db\\RowGateway\\AbstractRowGateway' => $vendorDir . '/zendframework/zend-db/src/RowGateway/AbstractRowGateway.php', + 'Zend\\Db\\RowGateway\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/RowGateway/Exception/ExceptionInterface.php', + 'Zend\\Db\\RowGateway\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-db/src/RowGateway/Exception/InvalidArgumentException.php', + 'Zend\\Db\\RowGateway\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-db/src/RowGateway/Exception/RuntimeException.php', + 'Zend\\Db\\RowGateway\\Feature\\AbstractFeature' => $vendorDir . '/zendframework/zend-db/src/RowGateway/Feature/AbstractFeature.php', + 'Zend\\Db\\RowGateway\\Feature\\FeatureSet' => $vendorDir . '/zendframework/zend-db/src/RowGateway/Feature/FeatureSet.php', + 'Zend\\Db\\RowGateway\\RowGateway' => $vendorDir . '/zendframework/zend-db/src/RowGateway/RowGateway.php', + 'Zend\\Db\\RowGateway\\RowGatewayInterface' => $vendorDir . '/zendframework/zend-db/src/RowGateway/RowGatewayInterface.php', + 'Zend\\Db\\Sql\\AbstractExpression' => $vendorDir . '/zendframework/zend-db/src/Sql/AbstractExpression.php', + 'Zend\\Db\\Sql\\AbstractPreparableSql' => $vendorDir . '/zendframework/zend-db/src/Sql/AbstractPreparableSql.php', + 'Zend\\Db\\Sql\\AbstractSql' => $vendorDir . '/zendframework/zend-db/src/Sql/AbstractSql.php', + 'Zend\\Db\\Sql\\Combine' => $vendorDir . '/zendframework/zend-db/src/Sql/Combine.php', + 'Zend\\Db\\Sql\\Ddl\\AlterTable' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/AlterTable.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\AbstractLengthColumn' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/AbstractLengthColumn.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\AbstractPrecisionColumn' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/AbstractPrecisionColumn.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\AbstractTimestampColumn' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/AbstractTimestampColumn.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\BigInteger' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/BigInteger.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Binary' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Binary.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Blob' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Blob.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Boolean' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Boolean.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Char' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Char.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Column' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Column.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\ColumnInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/ColumnInterface.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Date' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Date.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Datetime' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Datetime.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Decimal' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Decimal.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Float' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Float.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Floating' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Floating.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Integer' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Integer.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Text' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Text.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Time' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Time.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Timestamp' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Timestamp.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Varbinary' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Varbinary.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Varchar' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Column/Varchar.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\AbstractConstraint' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Constraint/AbstractConstraint.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\Check' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Constraint/Check.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\ConstraintInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Constraint/ConstraintInterface.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\ForeignKey' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Constraint/ForeignKey.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\PrimaryKey' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Constraint/PrimaryKey.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\UniqueKey' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Constraint/UniqueKey.php', + 'Zend\\Db\\Sql\\Ddl\\CreateTable' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/CreateTable.php', + 'Zend\\Db\\Sql\\Ddl\\DropTable' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/DropTable.php', + 'Zend\\Db\\Sql\\Ddl\\Index\\AbstractIndex' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Index/AbstractIndex.php', + 'Zend\\Db\\Sql\\Ddl\\Index\\Index' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/Index/Index.php', + 'Zend\\Db\\Sql\\Ddl\\SqlInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/Ddl/SqlInterface.php', + 'Zend\\Db\\Sql\\Delete' => $vendorDir . '/zendframework/zend-db/src/Sql/Delete.php', + 'Zend\\Db\\Sql\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/Exception/ExceptionInterface.php', + 'Zend\\Db\\Sql\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-db/src/Sql/Exception/InvalidArgumentException.php', + 'Zend\\Db\\Sql\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-db/src/Sql/Exception/RuntimeException.php', + 'Zend\\Db\\Sql\\Expression' => $vendorDir . '/zendframework/zend-db/src/Sql/Expression.php', + 'Zend\\Db\\Sql\\ExpressionInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/ExpressionInterface.php', + 'Zend\\Db\\Sql\\Having' => $vendorDir . '/zendframework/zend-db/src/Sql/Having.php', + 'Zend\\Db\\Sql\\Insert' => $vendorDir . '/zendframework/zend-db/src/Sql/Insert.php', + 'Zend\\Db\\Sql\\Join' => $vendorDir . '/zendframework/zend-db/src/Sql/Join.php', + 'Zend\\Db\\Sql\\Literal' => $vendorDir . '/zendframework/zend-db/src/Sql/Literal.php', + 'Zend\\Db\\Sql\\Platform\\AbstractPlatform' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/AbstractPlatform.php', + 'Zend\\Db\\Sql\\Platform\\IbmDb2\\IbmDb2' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/IbmDb2/IbmDb2.php', + 'Zend\\Db\\Sql\\Platform\\IbmDb2\\SelectDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/IbmDb2/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\Ddl\\AlterTableDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Mysql/Ddl/AlterTableDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\Ddl\\CreateTableDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Mysql/Ddl/CreateTableDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\Mysql' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Mysql/Mysql.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\SelectDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Mysql/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Oracle\\Oracle' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Oracle/Oracle.php', + 'Zend\\Db\\Sql\\Platform\\Oracle\\SelectDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Oracle/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Platform' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Platform.php', + 'Zend\\Db\\Sql\\Platform\\PlatformDecoratorInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/PlatformDecoratorInterface.php', + 'Zend\\Db\\Sql\\Platform\\SqlServer\\Ddl\\CreateTableDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/SqlServer/Ddl/CreateTableDecorator.php', + 'Zend\\Db\\Sql\\Platform\\SqlServer\\SelectDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/SqlServer/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\SqlServer\\SqlServer' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/SqlServer/SqlServer.php', + 'Zend\\Db\\Sql\\Platform\\Sqlite\\SelectDecorator' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Sqlite/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Sqlite\\Sqlite' => $vendorDir . '/zendframework/zend-db/src/Sql/Platform/Sqlite/Sqlite.php', + 'Zend\\Db\\Sql\\Predicate\\Between' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/Between.php', + 'Zend\\Db\\Sql\\Predicate\\Expression' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/Expression.php', + 'Zend\\Db\\Sql\\Predicate\\In' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/In.php', + 'Zend\\Db\\Sql\\Predicate\\IsNotNull' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/IsNotNull.php', + 'Zend\\Db\\Sql\\Predicate\\IsNull' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/IsNull.php', + 'Zend\\Db\\Sql\\Predicate\\Like' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/Like.php', + 'Zend\\Db\\Sql\\Predicate\\Literal' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/Literal.php', + 'Zend\\Db\\Sql\\Predicate\\NotBetween' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/NotBetween.php', + 'Zend\\Db\\Sql\\Predicate\\NotIn' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/NotIn.php', + 'Zend\\Db\\Sql\\Predicate\\NotLike' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/NotLike.php', + 'Zend\\Db\\Sql\\Predicate\\Operator' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/Operator.php', + 'Zend\\Db\\Sql\\Predicate\\Predicate' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/Predicate.php', + 'Zend\\Db\\Sql\\Predicate\\PredicateInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/PredicateInterface.php', + 'Zend\\Db\\Sql\\Predicate\\PredicateSet' => $vendorDir . '/zendframework/zend-db/src/Sql/Predicate/PredicateSet.php', + 'Zend\\Db\\Sql\\PreparableSqlInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/PreparableSqlInterface.php', + 'Zend\\Db\\Sql\\Select' => $vendorDir . '/zendframework/zend-db/src/Sql/Select.php', + 'Zend\\Db\\Sql\\Sql' => $vendorDir . '/zendframework/zend-db/src/Sql/Sql.php', + 'Zend\\Db\\Sql\\SqlInterface' => $vendorDir . '/zendframework/zend-db/src/Sql/SqlInterface.php', + 'Zend\\Db\\Sql\\TableIdentifier' => $vendorDir . '/zendframework/zend-db/src/Sql/TableIdentifier.php', + 'Zend\\Db\\Sql\\Update' => $vendorDir . '/zendframework/zend-db/src/Sql/Update.php', + 'Zend\\Db\\Sql\\Where' => $vendorDir . '/zendframework/zend-db/src/Sql/Where.php', + 'Zend\\Db\\TableGateway\\AbstractTableGateway' => $vendorDir . '/zendframework/zend-db/src/TableGateway/AbstractTableGateway.php', + 'Zend\\Db\\TableGateway\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Exception/ExceptionInterface.php', + 'Zend\\Db\\TableGateway\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Exception/InvalidArgumentException.php', + 'Zend\\Db\\TableGateway\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Exception/RuntimeException.php', + 'Zend\\Db\\TableGateway\\Feature\\AbstractFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/AbstractFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\EventFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/EventFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\EventFeatureEventsInterface' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/EventFeatureEventsInterface.php', + 'Zend\\Db\\TableGateway\\Feature\\EventFeature\\TableGatewayEvent' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php', + 'Zend\\Db\\TableGateway\\Feature\\FeatureSet' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/FeatureSet.php', + 'Zend\\Db\\TableGateway\\Feature\\GlobalAdapterFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/GlobalAdapterFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\MasterSlaveFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/MasterSlaveFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\MetadataFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/MetadataFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\RowGatewayFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/RowGatewayFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\SequenceFeature' => $vendorDir . '/zendframework/zend-db/src/TableGateway/Feature/SequenceFeature.php', + 'Zend\\Db\\TableGateway\\TableGateway' => $vendorDir . '/zendframework/zend-db/src/TableGateway/TableGateway.php', + 'Zend\\Db\\TableGateway\\TableGatewayInterface' => $vendorDir . '/zendframework/zend-db/src/TableGateway/TableGatewayInterface.php', + 'Zend\\Stdlib\\AbstractOptions' => $vendorDir . '/zendframework/zend-stdlib/src/AbstractOptions.php', + 'Zend\\Stdlib\\ArrayObject' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayObject.php', + 'Zend\\Stdlib\\ArraySerializableInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ArraySerializableInterface.php', + 'Zend\\Stdlib\\ArrayStack' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayStack.php', + 'Zend\\Stdlib\\ArrayUtils' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeRemoveKey' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKey' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Zend\\Stdlib\\ConsoleHelper' => $vendorDir . '/zendframework/zend-stdlib/src/ConsoleHelper.php', + 'Zend\\Stdlib\\DispatchableInterface' => $vendorDir . '/zendframework/zend-stdlib/src/DispatchableInterface.php', + 'Zend\\Stdlib\\ErrorHandler' => $vendorDir . '/zendframework/zend-stdlib/src/ErrorHandler.php', + 'Zend\\Stdlib\\Exception\\BadMethodCallException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/BadMethodCallException.php', + 'Zend\\Stdlib\\Exception\\DomainException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/DomainException.php', + 'Zend\\Stdlib\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/ExceptionInterface.php', + 'Zend\\Stdlib\\Exception\\ExtensionNotLoadedException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Zend\\Stdlib\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/InvalidArgumentException.php', + 'Zend\\Stdlib\\Exception\\LogicException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/LogicException.php', + 'Zend\\Stdlib\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/RuntimeException.php', + 'Zend\\Stdlib\\FastPriorityQueue' => $vendorDir . '/zendframework/zend-stdlib/src/FastPriorityQueue.php', + 'Zend\\Stdlib\\Glob' => $vendorDir . '/zendframework/zend-stdlib/src/Glob.php', + 'Zend\\Stdlib\\Guard\\AllGuardsTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php', + 'Zend\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Zend\\Stdlib\\Guard\\EmptyGuardTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/EmptyGuardTrait.php', + 'Zend\\Stdlib\\Guard\\NullGuardTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/NullGuardTrait.php', + 'Zend\\Stdlib\\InitializableInterface' => $vendorDir . '/zendframework/zend-stdlib/src/InitializableInterface.php', + 'Zend\\Stdlib\\JsonSerializable' => $vendorDir . '/zendframework/zend-stdlib/src/JsonSerializable.php', + 'Zend\\Stdlib\\Message' => $vendorDir . '/zendframework/zend-stdlib/src/Message.php', + 'Zend\\Stdlib\\MessageInterface' => $vendorDir . '/zendframework/zend-stdlib/src/MessageInterface.php', + 'Zend\\Stdlib\\ParameterObjectInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ParameterObjectInterface.php', + 'Zend\\Stdlib\\Parameters' => $vendorDir . '/zendframework/zend-stdlib/src/Parameters.php', + 'Zend\\Stdlib\\ParametersInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ParametersInterface.php', + 'Zend\\Stdlib\\PriorityList' => $vendorDir . '/zendframework/zend-stdlib/src/PriorityList.php', + 'Zend\\Stdlib\\PriorityQueue' => $vendorDir . '/zendframework/zend-stdlib/src/PriorityQueue.php', + 'Zend\\Stdlib\\Request' => $vendorDir . '/zendframework/zend-stdlib/src/Request.php', + 'Zend\\Stdlib\\RequestInterface' => $vendorDir . '/zendframework/zend-stdlib/src/RequestInterface.php', + 'Zend\\Stdlib\\Response' => $vendorDir . '/zendframework/zend-stdlib/src/Response.php', + 'Zend\\Stdlib\\ResponseInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ResponseInterface.php', + 'Zend\\Stdlib\\SplPriorityQueue' => $vendorDir . '/zendframework/zend-stdlib/src/SplPriorityQueue.php', + 'Zend\\Stdlib\\SplQueue' => $vendorDir . '/zendframework/zend-stdlib/src/SplQueue.php', + 'Zend\\Stdlib\\SplStack' => $vendorDir . '/zendframework/zend-stdlib/src/SplStack.php', + 'Zend\\Stdlib\\StringUtils' => $vendorDir . '/zendframework/zend-stdlib/src/StringUtils.php', + 'Zend\\Stdlib\\StringWrapper\\AbstractStringWrapper' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Zend\\Stdlib\\StringWrapper\\Iconv' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/Iconv.php', + 'Zend\\Stdlib\\StringWrapper\\Intl' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/Intl.php', + 'Zend\\Stdlib\\StringWrapper\\MbString' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/MbString.php', + 'Zend\\Stdlib\\StringWrapper\\Native' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/Native.php', + 'Zend\\Stdlib\\StringWrapper\\StringWrapperInterface' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'phpDocumentor\\Reflection\\DocBlock' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock.php', + 'phpDocumentor\\Reflection\\DocBlockFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php', + 'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php', + 'phpDocumentor\\Reflection\\DocBlock\\Description' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php', + 'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php', + 'phpDocumentor\\Reflection\\DocBlock\\Serializer' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php', + 'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php', + 'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\Strategy' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php', + 'phpDocumentor\\Reflection\\Element' => $vendorDir . '/phpdocumentor/reflection-common/src/Element.php', + 'phpDocumentor\\Reflection\\File' => $vendorDir . '/phpdocumentor/reflection-common/src/File.php', + 'phpDocumentor\\Reflection\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-common/src/Fqsen.php', + 'phpDocumentor\\Reflection\\FqsenResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/FqsenResolver.php', + 'phpDocumentor\\Reflection\\Location' => $vendorDir . '/phpdocumentor/reflection-common/src/Location.php', + 'phpDocumentor\\Reflection\\Project' => $vendorDir . '/phpdocumentor/reflection-common/src/Project.php', + 'phpDocumentor\\Reflection\\ProjectFactory' => $vendorDir . '/phpdocumentor/reflection-common/src/ProjectFactory.php', + 'phpDocumentor\\Reflection\\Type' => $vendorDir . '/phpdocumentor/type-resolver/src/Type.php', + 'phpDocumentor\\Reflection\\TypeResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/TypeResolver.php', + 'phpDocumentor\\Reflection\\Types\\Array_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Array_.php', + 'phpDocumentor\\Reflection\\Types\\Boolean' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Boolean.php', + 'phpDocumentor\\Reflection\\Types\\Callable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Callable_.php', + 'phpDocumentor\\Reflection\\Types\\Compound' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Compound.php', + 'phpDocumentor\\Reflection\\Types\\Context' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Context.php', + 'phpDocumentor\\Reflection\\Types\\ContextFactory' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php', + 'phpDocumentor\\Reflection\\Types\\Float_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Float_.php', + 'phpDocumentor\\Reflection\\Types\\Integer' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Integer.php', + 'phpDocumentor\\Reflection\\Types\\Iterable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Iterable_.php', + 'phpDocumentor\\Reflection\\Types\\Mixed_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Mixed_.php', + 'phpDocumentor\\Reflection\\Types\\Null_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Null_.php', + 'phpDocumentor\\Reflection\\Types\\Nullable' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Nullable.php', + 'phpDocumentor\\Reflection\\Types\\Object_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Object_.php', + 'phpDocumentor\\Reflection\\Types\\Parent_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Parent_.php', + 'phpDocumentor\\Reflection\\Types\\Resource_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Resource_.php', + 'phpDocumentor\\Reflection\\Types\\Scalar' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Scalar.php', + 'phpDocumentor\\Reflection\\Types\\Self_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Self_.php', + 'phpDocumentor\\Reflection\\Types\\Static_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Static_.php', + 'phpDocumentor\\Reflection\\Types\\String_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/String_.php', + 'phpDocumentor\\Reflection\\Types\\This' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/This.php', + 'phpDocumentor\\Reflection\\Types\\Void_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Void_.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000000..5e176ab873 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,19 @@ + $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + '253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php', + '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', + '6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', + 'cf0c1b33f2c95076010ff23d7e02d3ac' => $baseDir . '/src/helpers/all.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000000..88382927d4 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,12 @@ + array($vendorDir . '/twig/twig/lib'), + 'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src'), + 'Pimple' => array($vendorDir . '/pimple/pimple/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000000..aa8c9ce1a6 --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,55 @@ + array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-docblock/src'), + 'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib/src'), + 'Zend\\Db\\' => array($vendorDir . '/zendframework/zend-db/src'), + 'WellingGuzman\\OAuth2\\Client\\' => array($vendorDir . '/wellingguzman/oauth2-okta/src'), + 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), + 'Twig\\' => array($vendorDir . '/twig/twig/src'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\Validator\\' => array($vendorDir . '/symfony/validator'), + 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), + 'Slim\\Views\\' => array($vendorDir . '/slim/twig-view/src'), + 'Slim\\' => array($vendorDir . '/slim/slim/Slim'), + 'RateLimit\\' => array($vendorDir . '/wellingguzman/rate-limit/src'), + 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), + 'RKA\\Middleware\\' => array($vendorDir . '/akrabat/rka-ip-address-middleware/src'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'Phinx\\' => array($vendorDir . '/robmorgan/phinx/src/Phinx'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-github/src', $vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-facebook/src'), + 'League\\OAuth1\\' => array($vendorDir . '/league/oauth1-client/src'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), + 'Intervention\\Image\\' => array($vendorDir . '/intervention/image/src/Intervention/Image'), + 'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), + 'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'), + 'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), + 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache'), + 'Directus\\Custom\\Hooks\\' => array($baseDir . '/public/extensions/custom/hooks'), + 'Directus\\Custom\\Hasher\\' => array($baseDir . '/public/extensions/custom/hashers'), + 'Directus\\Custom\\Embed\\Provider\\' => array($baseDir . '/public/extensions/custom/embeds'), + 'Directus\\Authentication\\Sso\\Provider\\' => array($baseDir . '/public/extensions/core/auth'), + 'Directus\\Api\\Routes\\' => array($baseDir . '/src/endpoints'), + 'Directus\\' => array($baseDir . '/src/core/Directus'), + 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), + 'Cache\\' => array($vendorDir . '/cache/cache/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000000..2e751b132e --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,61 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit989a4969ed6a6e584179ae2090c4eecd::getInitializer($loader)); + } else { + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->setClassMapAuthoritative(true); + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit989a4969ed6a6e584179ae2090c4eecd::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire989a4969ed6a6e584179ae2090c4eecd($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire989a4969ed6a6e584179ae2090c4eecd($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000000..c3da747294 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,2950 @@ + __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + '253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php', + '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', + '6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', + 'cf0c1b33f2c95076010ff23d7e02d3ac' => __DIR__ . '/../..' . '/src/helpers/all.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'p' => + array ( + 'phpDocumentor\\Reflection\\' => 25, + ), + 'Z' => + array ( + 'Zend\\Stdlib\\' => 12, + 'Zend\\Db\\' => 8, + ), + 'W' => + array ( + 'WellingGuzman\\OAuth2\\Client\\' => 28, + 'Webmozart\\Assert\\' => 17, + ), + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\Validator\\' => 28, + 'Symfony\\Component\\Translation\\' => 30, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\Console\\' => 26, + 'Symfony\\Component\\Config\\' => 25, + 'Slim\\Views\\' => 11, + 'Slim\\' => 5, + ), + 'R' => + array ( + 'RateLimit\\' => 10, + 'Ramsey\\Uuid\\' => 12, + 'RKA\\Middleware\\' => 15, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + 'Phinx\\' => 6, + ), + 'M' => + array ( + 'Monolog\\' => 8, + ), + 'L' => + array ( + 'League\\OAuth2\\Client\\' => 21, + 'League\\OAuth1\\' => 14, + 'League\\Flysystem\\' => 17, + ), + 'I' => + array ( + 'Intervention\\Image\\' => 19, + 'Interop\\Container\\' => 18, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'F' => + array ( + 'Firebase\\JWT\\' => 13, + 'FastRoute\\' => 10, + ), + 'D' => + array ( + 'Doctrine\\Instantiator\\' => 22, + 'Doctrine\\Common\\Cache\\' => 22, + 'Directus\\Custom\\Hooks\\' => 22, + 'Directus\\Custom\\Hasher\\' => 23, + 'Directus\\Custom\\Embed\\Provider\\' => 31, + 'Directus\\Authentication\\Sso\\Provider\\' => 37, + 'Directus\\Api\\Routes\\' => 20, + 'Directus\\' => 9, + 'DeepCopy\\' => 9, + ), + 'C' => + array ( + 'Cache\\' => 6, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'phpDocumentor\\Reflection\\' => + array ( + 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', + 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', + 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', + ), + 'Zend\\Stdlib\\' => + array ( + 0 => __DIR__ . '/..' . '/zendframework/zend-stdlib/src', + ), + 'Zend\\Db\\' => + array ( + 0 => __DIR__ . '/..' . '/zendframework/zend-db/src', + ), + 'WellingGuzman\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/wellingguzman/oauth2-okta/src', + ), + 'Webmozart\\Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/assert/src', + ), + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + 'Symfony\\Component\\Validator\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/validator', + ), + 'Symfony\\Component\\Translation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/translation', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Symfony\\Component\\Config\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/config', + ), + 'Slim\\Views\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/twig-view/src', + ), + 'Slim\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/slim/Slim', + ), + 'RateLimit\\' => + array ( + 0 => __DIR__ . '/..' . '/wellingguzman/rate-limit/src', + ), + 'Ramsey\\Uuid\\' => + array ( + 0 => __DIR__ . '/..' . '/ramsey/uuid/src', + ), + 'RKA\\Middleware\\' => + array ( + 0 => __DIR__ . '/..' . '/akrabat/rka-ip-address-middleware/src', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'Phinx\\' => + array ( + 0 => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'League\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + 1 => __DIR__ . '/..' . '/league/oauth2-github/src', + 2 => __DIR__ . '/..' . '/league/oauth2-google/src', + 3 => __DIR__ . '/..' . '/league/oauth2-facebook/src', + ), + 'League\\OAuth1\\' => + array ( + 0 => __DIR__ . '/..' . '/league/oauth1-client/src', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + 'Intervention\\Image\\' => + array ( + 0 => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image', + ), + 'Interop\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'Firebase\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', + ), + 'FastRoute\\' => + array ( + 0 => __DIR__ . '/..' . '/nikic/fast-route/src', + ), + 'Doctrine\\Instantiator\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', + ), + 'Doctrine\\Common\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache', + ), + 'Directus\\Custom\\Hooks\\' => + array ( + 0 => __DIR__ . '/../..' . '/public/extensions/custom/hooks', + ), + 'Directus\\Custom\\Hasher\\' => + array ( + 0 => __DIR__ . '/../..' . '/public/extensions/custom/hashers', + ), + 'Directus\\Custom\\Embed\\Provider\\' => + array ( + 0 => __DIR__ . '/../..' . '/public/extensions/custom/embeds', + ), + 'Directus\\Authentication\\Sso\\Provider\\' => + array ( + 0 => __DIR__ . '/../..' . '/public/extensions/core/auth', + ), + 'Directus\\Api\\Routes\\' => + array ( + 0 => __DIR__ . '/../..' . '/src/endpoints', + ), + 'Directus\\' => + array ( + 0 => __DIR__ . '/../..' . '/src/core/Directus', + ), + 'DeepCopy\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy', + ), + 'Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/cache/cache/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'T' => + array ( + 'Twig_' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/lib', + ), + ), + 'P' => + array ( + 'Prophecy\\' => + array ( + 0 => __DIR__ . '/..' . '/phpspec/prophecy/src', + ), + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/src', + ), + ), + ); + + public static $classMap = array ( + 'Cache\\Adapter\\Apc\\ApcCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Apc/ApcCachePool.php', + 'Cache\\Adapter\\Apcu\\ApcuCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Apcu/ApcuCachePool.php', + 'Cache\\Adapter\\Chain\\CachePoolChain' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Chain/CachePoolChain.php', + 'Cache\\Adapter\\Chain\\Exception\\NoPoolAvailableException' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Chain/Exception/NoPoolAvailableException.php', + 'Cache\\Adapter\\Chain\\Exception\\PoolFailedException' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Chain/Exception/PoolFailedException.php', + 'Cache\\Adapter\\Common\\AbstractCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/AbstractCachePool.php', + 'Cache\\Adapter\\Common\\CacheItem' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/CacheItem.php', + 'Cache\\Adapter\\Common\\Exception\\CacheException' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/Exception/CacheException.php', + 'Cache\\Adapter\\Common\\Exception\\CachePoolException' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/Exception/CachePoolException.php', + 'Cache\\Adapter\\Common\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/Exception/InvalidArgumentException.php', + 'Cache\\Adapter\\Common\\HasExpirationTimestampInterface' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/HasExpirationTimestampInterface.php', + 'Cache\\Adapter\\Common\\PhpCacheItem' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/PhpCacheItem.php', + 'Cache\\Adapter\\Common\\PhpCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/PhpCachePool.php', + 'Cache\\Adapter\\Common\\TagSupportWithArray' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Common/TagSupportWithArray.php', + 'Cache\\Adapter\\Doctrine\\DoctrineCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Doctrine/DoctrineCachePool.php', + 'Cache\\Adapter\\Filesystem\\FilesystemCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Filesystem/FilesystemCachePool.php', + 'Cache\\Adapter\\Illuminate\\IlluminateCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Illuminate/IlluminateCachePool.php', + 'Cache\\Adapter\\Memcache\\MemcacheCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Memcache/MemcacheCachePool.php', + 'Cache\\Adapter\\Memcached\\MemcachedCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Memcached/MemcachedCachePool.php', + 'Cache\\Adapter\\MongoDB\\MongoDBCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/MongoDB/MongoDBCachePool.php', + 'Cache\\Adapter\\PHPArray\\ArrayCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/PHPArray/ArrayCachePool.php', + 'Cache\\Adapter\\Predis\\PredisCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Predis/PredisCachePool.php', + 'Cache\\Adapter\\Redis\\RedisCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Redis/RedisCachePool.php', + 'Cache\\Adapter\\Void\\VoidCachePool' => __DIR__ . '/..' . '/cache/cache/src/Adapter/Void/VoidCachePool.php', + 'Cache\\Bridge\\Doctrine\\DoctrineCacheBridge' => __DIR__ . '/..' . '/cache/cache/src/Bridge/Doctrine/DoctrineCacheBridge.php', + 'Cache\\Bridge\\SimpleCache\\Exception\\CacheException' => __DIR__ . '/..' . '/cache/cache/src/Bridge/SimpleCache/Exception/CacheException.php', + 'Cache\\Bridge\\SimpleCache\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/cache/cache/src/Bridge/SimpleCache/Exception/InvalidArgumentException.php', + 'Cache\\Bridge\\SimpleCache\\SimpleCacheBridge' => __DIR__ . '/..' . '/cache/cache/src/Bridge/SimpleCache/SimpleCacheBridge.php', + 'Cache\\Encryption\\EncryptedCachePool' => __DIR__ . '/..' . '/cache/cache/src/Encryption/EncryptedCachePool.php', + 'Cache\\Encryption\\EncryptedItemDecorator' => __DIR__ . '/..' . '/cache/cache/src/Encryption/EncryptedItemDecorator.php', + 'Cache\\Hierarchy\\HierarchicalCachePoolTrait' => __DIR__ . '/..' . '/cache/cache/src/Hierarchy/HierarchicalCachePoolTrait.php', + 'Cache\\Hierarchy\\HierarchicalPoolInterface' => __DIR__ . '/..' . '/cache/cache/src/Hierarchy/HierarchicalPoolInterface.php', + 'Cache\\Namespaced\\NamespacedCachePool' => __DIR__ . '/..' . '/cache/cache/src/Namespaced/NamespacedCachePool.php', + 'Cache\\Prefixed\\PrefixedCachePool' => __DIR__ . '/..' . '/cache/cache/src/Prefixed/PrefixedCachePool.php', + 'Cache\\SessionHandler\\Psr6SessionHandler' => __DIR__ . '/..' . '/cache/cache/src/SessionHandler/Psr6SessionHandler.php', + 'Cache\\TagInterop\\TaggableCacheItemInterface' => __DIR__ . '/..' . '/cache/cache/src/TagInterop/TaggableCacheItemInterface.php', + 'Cache\\TagInterop\\TaggableCacheItemPoolInterface' => __DIR__ . '/..' . '/cache/cache/src/TagInterop/TaggableCacheItemPoolInterface.php', + 'Cache\\Taggable\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/cache/cache/src/Taggable/Exception/InvalidArgumentException.php', + 'Cache\\Taggable\\TaggablePSR6ItemAdapter' => __DIR__ . '/..' . '/cache/cache/src/Taggable/TaggablePSR6ItemAdapter.php', + 'Cache\\Taggable\\TaggablePSR6PoolAdapter' => __DIR__ . '/..' . '/cache/cache/src/Taggable/TaggablePSR6PoolAdapter.php', + 'DeepCopy\\DeepCopy' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/DeepCopy.php', + 'DeepCopy\\Exception\\CloneException' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php', + 'DeepCopy\\Exception\\PropertyException' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Exception/PropertyException.php', + 'DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php', + 'DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php', + 'DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php', + 'DeepCopy\\Filter\\Filter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php', + 'DeepCopy\\Filter\\KeepFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php', + 'DeepCopy\\Filter\\ReplaceFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php', + 'DeepCopy\\Filter\\SetNullFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php', + 'DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php', + 'DeepCopy\\Matcher\\Matcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.php', + 'DeepCopy\\Matcher\\PropertyMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.php', + 'DeepCopy\\Matcher\\PropertyNameMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php', + 'DeepCopy\\Matcher\\PropertyTypeMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php', + 'DeepCopy\\Reflection\\ReflectionHelper' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php', + 'DeepCopy\\TypeFilter\\Date\\DateIntervalFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php', + 'DeepCopy\\TypeFilter\\ReplaceFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php', + 'DeepCopy\\TypeFilter\\ShallowCopyFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php', + 'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedList' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php', + 'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php', + 'DeepCopy\\TypeFilter\\TypeFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php', + 'DeepCopy\\TypeMatcher\\TypeMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.php', + 'Directus\\Api\\Routes\\Activity' => __DIR__ . '/../..' . '/src/endpoints/Activity.php', + 'Directus\\Api\\Routes\\Auth' => __DIR__ . '/../..' . '/src/endpoints/Auth.php', + 'Directus\\Api\\Routes\\CollectionPresets' => __DIR__ . '/../..' . '/src/endpoints/CollectionPresets.php', + 'Directus\\Api\\Routes\\Collections' => __DIR__ . '/../..' . '/src/endpoints/Collections.php', + 'Directus\\Api\\Routes\\Fields' => __DIR__ . '/../..' . '/src/endpoints/Fields.php', + 'Directus\\Api\\Routes\\Files' => __DIR__ . '/../..' . '/src/endpoints/Files.php', + 'Directus\\Api\\Routes\\Home' => __DIR__ . '/../..' . '/src/endpoints/Home.php', + 'Directus\\Api\\Routes\\Interfaces' => __DIR__ . '/../..' . '/src/endpoints/Interfaces.php', + 'Directus\\Api\\Routes\\Items' => __DIR__ . '/../..' . '/src/endpoints/Items.php', + 'Directus\\Api\\Routes\\Listings' => __DIR__ . '/../..' . '/src/endpoints/Listings.php', + 'Directus\\Api\\Routes\\Pages' => __DIR__ . '/../..' . '/src/endpoints/Pages.php', + 'Directus\\Api\\Routes\\Permissions' => __DIR__ . '/../..' . '/src/endpoints/Permissions.php', + 'Directus\\Api\\Routes\\Relations' => __DIR__ . '/../..' . '/src/endpoints/Relations.php', + 'Directus\\Api\\Routes\\Revisions' => __DIR__ . '/../..' . '/src/endpoints/Revisions.php', + 'Directus\\Api\\Routes\\Roles' => __DIR__ . '/../..' . '/src/endpoints/Roles.php', + 'Directus\\Api\\Routes\\ScimTwo' => __DIR__ . '/../..' . '/src/endpoints/ScimTwo.php', + 'Directus\\Api\\Routes\\Server' => __DIR__ . '/../..' . '/src/endpoints/Server.php', + 'Directus\\Api\\Routes\\Settings' => __DIR__ . '/../..' . '/src/endpoints/Settings.php', + 'Directus\\Api\\Routes\\Types' => __DIR__ . '/../..' . '/src/endpoints/Types.php', + 'Directus\\Api\\Routes\\Users' => __DIR__ . '/../..' . '/src/endpoints/Users.php', + 'Directus\\Api\\Routes\\Utils' => __DIR__ . '/../..' . '/src/endpoints/Utils.php', + 'Directus\\Application\\Application' => __DIR__ . '/../..' . '/src/core/Directus/Application/Application.php', + 'Directus\\Application\\Container' => __DIR__ . '/../..' . '/src/core/Directus/Application/Container.php', + 'Directus\\Application\\CoreServicesProvider' => __DIR__ . '/../..' . '/src/core/Directus/Application/CoreServicesProvider.php', + 'Directus\\Application\\DefaultServicesProvider' => __DIR__ . '/../..' . '/src/core/Directus/Application/DefaultServicesProvider.php', + 'Directus\\Application\\ErrorHandlers\\ErrorHandler' => __DIR__ . '/../..' . '/src/core/Directus/Application/ErrorHandlers/ErrorHandler.php', + 'Directus\\Application\\ErrorHandlers\\MethodNotAllowedHandler' => __DIR__ . '/../..' . '/src/core/Directus/Application/ErrorHandlers/MethodNotAllowedHandler.php', + 'Directus\\Application\\ErrorHandlers\\NotFoundHandler' => __DIR__ . '/../..' . '/src/core/Directus/Application/ErrorHandlers/NotFoundHandler.php', + 'Directus\\Application\\Http\\Middleware\\AbstractMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/AbstractMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AbstractRateLimitMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/AbstractRateLimitMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AdminMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/AdminMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AuthenticatedMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/AuthenticatedMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\AuthenticationMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/AuthenticationMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\CorsMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/CorsMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\IpRateLimitMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/IpRateLimitMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\RateLimit\\UserIdentityResolver' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/RateLimit/UserIdentityResolver.php', + 'Directus\\Application\\Http\\Middleware\\ResponseCacheMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/ResponseCacheMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\TableGatewayMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/TableGatewayMiddleware.php', + 'Directus\\Application\\Http\\Middleware\\UserRateLimitMiddleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/UserRateLimitMiddleware.php', + 'Directus\\Application\\Http\\Request' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Request.php', + 'Directus\\Application\\Http\\Response' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Response.php', + 'Directus\\Application\\Route' => __DIR__ . '/../..' . '/src/core/Directus/Application/Route.php', + 'Directus\\Authentication\\Exception\\ExpiredRequestTokenException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/ExpiredRequestTokenException.php', + 'Directus\\Authentication\\Exception\\ExpiredResetPasswordToken' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/ExpiredResetPasswordToken.php', + 'Directus\\Authentication\\Exception\\ExpiredTokenException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/ExpiredTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidInvitationCodeException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/InvalidInvitationCodeException.php', + 'Directus\\Authentication\\Exception\\InvalidRequestTokenException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/InvalidRequestTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidResetPasswordTokenException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/InvalidResetPasswordTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidTokenException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/InvalidTokenException.php', + 'Directus\\Authentication\\Exception\\InvalidUserCredentialsException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/InvalidUserCredentialsException.php', + 'Directus\\Authentication\\Exception\\UnknownUserAttributeException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/UnknownUserAttributeException.php', + 'Directus\\Authentication\\Exception\\UserAlreadyLoggedInException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/UserAlreadyLoggedInException.php', + 'Directus\\Authentication\\Exception\\UserInactiveException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/UserInactiveException.php', + 'Directus\\Authentication\\Exception\\UserNotAuthenticatedException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/UserNotAuthenticatedException.php', + 'Directus\\Authentication\\Exception\\UserNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/UserNotFoundException.php', + 'Directus\\Authentication\\Exception\\UserWithEmailNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Exception/UserWithEmailNotFoundException.php', + 'Directus\\Authentication\\Provider' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Provider.php', + 'Directus\\Authentication\\Sso\\AbstractSocialProvider' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Sso/AbstractSocialProvider.php', + 'Directus\\Authentication\\Sso\\OneSocialProvider' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Sso/OneSocialProvider.php', + 'Directus\\Authentication\\Sso\\Provider\\facebook\\Provider' => __DIR__ . '/../..' . '/public/extensions/core/auth/facebook/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\github\\Provider' => __DIR__ . '/../..' . '/public/extensions/core/auth/github/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\google\\Provider' => __DIR__ . '/../..' . '/public/extensions/core/auth/google/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\okta\\Provider' => __DIR__ . '/../..' . '/public/extensions/core/auth/okta/Provider.php', + 'Directus\\Authentication\\Sso\\Provider\\twitter\\Provider' => __DIR__ . '/../..' . '/public/extensions/core/auth/twitter/Provider.php', + 'Directus\\Authentication\\Sso\\Social' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Sso/Social.php', + 'Directus\\Authentication\\Sso\\SocialProviderInterface' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Sso/SocialProviderInterface.php', + 'Directus\\Authentication\\Sso\\SocialUser' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Sso/SocialUser.php', + 'Directus\\Authentication\\Sso\\TwoSocialProvider' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/Sso/TwoSocialProvider.php', + 'Directus\\Authentication\\User\\Provider\\UserProviderInterface' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/User/Provider/UserProviderInterface.php', + 'Directus\\Authentication\\User\\Provider\\UserTableGatewayProvider' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/User/Provider/UserTableGatewayProvider.php', + 'Directus\\Authentication\\User\\User' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/User/User.php', + 'Directus\\Authentication\\User\\UserInterface' => __DIR__ . '/../..' . '/src/core/Directus/Authentication/User/UserInterface.php', + 'Directus\\Cache\\Cache' => __DIR__ . '/../..' . '/src/core/Directus/Cache/Cache.php', + 'Directus\\Cache\\Response' => __DIR__ . '/../..' . '/src/core/Directus/Cache/Response.php', + 'Directus\\Collection\\Arrayable' => __DIR__ . '/../..' . '/src/core/Directus/Collection/Arrayable.php', + 'Directus\\Collection\\Collection' => __DIR__ . '/../..' . '/src/core/Directus/Collection/Collection.php', + 'Directus\\Collection\\CollectionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Collection/CollectionInterface.php', + 'Directus\\Config\\Config' => __DIR__ . '/../..' . '/src/core/Directus/Config/Config.php', + 'Directus\\Config\\ConfigInterface' => __DIR__ . '/../..' . '/src/core/Directus/Config/ConfigInterface.php', + 'Directus\\Config\\Exception\\InvalidStatusException' => __DIR__ . '/../..' . '/src/core/Directus/Config/Exception/InvalidStatusException.php', + 'Directus\\Config\\Exception\\InvalidValueException' => __DIR__ . '/../..' . '/src/core/Directus/Config/Exception/InvalidValueException.php', + 'Directus\\Config\\StatusItem' => __DIR__ . '/../..' . '/src/core/Directus/Config/StatusItem.php', + 'Directus\\Config\\StatusMapping' => __DIR__ . '/../..' . '/src/core/Directus/Config/StatusMapping.php', + 'Directus\\Console\\Cli' => __DIR__ . '/../..' . '/src/core/Directus/Console/Cli.php', + 'Directus\\Console\\Common\\Exception\\PasswordChangeException' => __DIR__ . '/../..' . '/src/core/Directus/Console/Common/Exception/PasswordChangeException.php', + 'Directus\\Console\\Common\\Exception\\SettingUpdateException' => __DIR__ . '/../..' . '/src/core/Directus/Console/Common/Exception/SettingUpdateException.php', + 'Directus\\Console\\Common\\Exception\\UserUpdateException' => __DIR__ . '/../..' . '/src/core/Directus/Console/Common/Exception/UserUpdateException.php', + 'Directus\\Console\\Common\\Setting' => __DIR__ . '/../..' . '/src/core/Directus/Console/Common/Setting.php', + 'Directus\\Console\\Common\\User' => __DIR__ . '/../..' . '/src/core/Directus/Console/Common/User.php', + 'Directus\\Console\\Exception\\CommandFailedException' => __DIR__ . '/../..' . '/src/core/Directus/Console/Exception/CommandFailedException.php', + 'Directus\\Console\\Exception\\UnsupportedCommandException' => __DIR__ . '/../..' . '/src/core/Directus/Console/Exception/UnsupportedCommandException.php', + 'Directus\\Console\\Exception\\WrongArgumentsException' => __DIR__ . '/../..' . '/src/core/Directus/Console/Exception/WrongArgumentsException.php', + 'Directus\\Console\\Modules\\CacheModule' => __DIR__ . '/../..' . '/src/core/Directus/Console/Modules/CacheModule.php', + 'Directus\\Console\\Modules\\DatabaseModule' => __DIR__ . '/../..' . '/src/core/Directus/Console/Modules/DatabaseModule.php', + 'Directus\\Console\\Modules\\InstallModule' => __DIR__ . '/../..' . '/src/core/Directus/Console/Modules/InstallModule.php', + 'Directus\\Console\\Modules\\ModuleBase' => __DIR__ . '/../..' . '/src/core/Directus/Console/Modules/ModuleBase.php', + 'Directus\\Console\\Modules\\ModuleInterface' => __DIR__ . '/../..' . '/src/core/Directus/Console/Modules/ModuleInterface.php', + 'Directus\\Console\\Modules\\UserModule' => __DIR__ . '/../..' . '/src/core/Directus/Console/Modules/UserModule.php', + 'Directus\\Container\\Container' => __DIR__ . '/../..' . '/src/core/Directus/Container/Container.php', + 'Directus\\Container\\Exception\\ValueNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Container/Exception/ValueNotFoundException.php', + 'Directus\\Custom\\Hasher\\CustomHasher' => __DIR__ . '/../..' . '/public/extensions/custom/hashers/_CustomHasher.php', + 'Directus\\Custom\\Hooks\\Products\\BeforeInsertProducts' => __DIR__ . '/../..' . '/public/extensions/custom/hooks/_products/BeforeInsertProducts.php', + 'Directus\\Database\\Connection' => __DIR__ . '/../..' . '/src/core/Directus/Database/Connection.php', + 'Directus\\Database\\Ddl\\Column\\Bit' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Bit.php', + 'Directus\\Database\\Ddl\\Column\\Boolean' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Boolean.php', + 'Directus\\Database\\Ddl\\Column\\CollectionLength' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/CollectionLength.php', + 'Directus\\Database\\Ddl\\Column\\Custom' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Custom.php', + 'Directus\\Database\\Ddl\\Column\\Double' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Double.php', + 'Directus\\Database\\Ddl\\Column\\Enum' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Enum.php', + 'Directus\\Database\\Ddl\\Column\\File' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/File.php', + 'Directus\\Database\\Ddl\\Column\\LongBlob' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/LongBlob.php', + 'Directus\\Database\\Ddl\\Column\\LongText' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/LongText.php', + 'Directus\\Database\\Ddl\\Column\\MediumBlob' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/MediumBlob.php', + 'Directus\\Database\\Ddl\\Column\\MediumInteger' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/MediumInteger.php', + 'Directus\\Database\\Ddl\\Column\\MediumText' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/MediumText.php', + 'Directus\\Database\\Ddl\\Column\\Numeric' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Numeric.php', + 'Directus\\Database\\Ddl\\Column\\Real' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Real.php', + 'Directus\\Database\\Ddl\\Column\\Serial' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Serial.php', + 'Directus\\Database\\Ddl\\Column\\Set' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Set.php', + 'Directus\\Database\\Ddl\\Column\\SmallInteger' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/SmallInteger.php', + 'Directus\\Database\\Ddl\\Column\\TinyBlob' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/TinyBlob.php', + 'Directus\\Database\\Ddl\\Column\\TinyInteger' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/TinyInteger.php', + 'Directus\\Database\\Ddl\\Column\\TinyText' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/TinyText.php', + 'Directus\\Database\\Ddl\\Column\\Uuid' => __DIR__ . '/../..' . '/src/core/Directus/Database/Ddl/Column/Uuid.php', + 'Directus\\Database\\Exception\\CollectionAlreadyExistsException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/CollectionAlreadyExistsException.php', + 'Directus\\Database\\Exception\\CollectionHasNotStatusInterfaceException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/CollectionHasNotStatusInterfaceException.php', + 'Directus\\Database\\Exception\\CollectionNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/CollectionNotFoundException.php', + 'Directus\\Database\\Exception\\CollectionNotManagedException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/CollectionNotManagedException.php', + 'Directus\\Database\\Exception\\ConnectionFailedException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/ConnectionFailedException.php', + 'Directus\\Database\\Exception\\CustomUiValidationError' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/CustomUiValidationError.php', + 'Directus\\Database\\Exception\\DbException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/DbException.php', + 'Directus\\Database\\Exception\\DuplicateItemException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/DuplicateItemException.php', + 'Directus\\Database\\Exception\\FieldAlreadyExistsException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/FieldAlreadyExistsException.php', + 'Directus\\Database\\Exception\\FieldAlreadyHasUniqueKeyException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/FieldAlreadyHasUniqueKeyException.php', + 'Directus\\Database\\Exception\\FieldNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/FieldNotFoundException.php', + 'Directus\\Database\\Exception\\FieldNotManagedException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/FieldNotManagedException.php', + 'Directus\\Database\\Exception\\ForbiddenSystemTableDirectAccessException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/ForbiddenSystemTableDirectAccessException.php', + 'Directus\\Database\\Exception\\InvalidFieldException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/InvalidFieldException.php', + 'Directus\\Database\\Exception\\InvalidQueryException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/InvalidQueryException.php', + 'Directus\\Database\\Exception\\ItemNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/ItemNotFoundException.php', + 'Directus\\Database\\Exception\\RelationshipMetadataException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/RelationshipMetadataException.php', + 'Directus\\Database\\Exception\\RevisionInvalidDeltaException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/RevisionInvalidDeltaException.php', + 'Directus\\Database\\Exception\\RevisionNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/RevisionNotFoundException.php', + 'Directus\\Database\\Exception\\StatusMappingEmptyException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/StatusMappingEmptyException.php', + 'Directus\\Database\\Exception\\StatusMappingWrongValueTypeException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/StatusMappingWrongValueTypeException.php', + 'Directus\\Database\\Exception\\SuppliedArrayAsColumnValue' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/SuppliedArrayAsColumnValue.php', + 'Directus\\Database\\Exception\\UnknownDataTypeException' => __DIR__ . '/../..' . '/src/core/Directus/Database/Exception/UnknownDataTypeException.php', + 'Directus\\Database\\Filters\\Filter' => __DIR__ . '/../..' . '/src/core/Directus/Database/Filters/Filter.php', + 'Directus\\Database\\Filters\\In' => __DIR__ . '/../..' . '/src/core/Directus/Database/Filters/In.php', + 'Directus\\Database\\Query\\Builder' => __DIR__ . '/../..' . '/src/core/Directus/Database/Query/Builder.php', + 'Directus\\Database\\Query\\Relations\\ManyToManyRelation' => __DIR__ . '/../..' . '/src/core/Directus/Database/Query/Relations/ManyToManyRelation.php', + 'Directus\\Database\\Query\\Relations\\ManyToOneRelation' => __DIR__ . '/../..' . '/src/core/Directus/Database/Query/Relations/ManyToOneRelation.php', + 'Directus\\Database\\Query\\Relations\\OneToManyRelation' => __DIR__ . '/../..' . '/src/core/Directus/Database/Query/Relations/OneToManyRelation.php', + 'Directus\\Database\\Repositories\\AbstractRepository' => __DIR__ . '/../..' . '/src/core/Directus/Database/Repositories/AbstractRepository.php', + 'Directus\\Database\\Repositories\\Repository' => __DIR__ . '/../..' . '/src/core/Directus/Database/Repositories/Repository.php', + 'Directus\\Database\\Repositories\\RepositoryFactory' => __DIR__ . '/../..' . '/src/core/Directus/Database/Repositories/RepositoryFactory.php', + 'Directus\\Database\\Repositories\\RepositoryInterface' => __DIR__ . '/../..' . '/src/core/Directus/Database/Repositories/RepositoryInterface.php', + 'Directus\\Database\\ResultItem' => __DIR__ . '/../..' . '/src/core/Directus/Database/ResultItem.php', + 'Directus\\Database\\ResultSet' => __DIR__ . '/../..' . '/src/core/Directus/Database/ResultSet.php', + 'Directus\\Database\\RowGateway\\BaseRowGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/RowGateway/BaseRowGateway.php', + 'Directus\\Database\\RowGateway\\DirectusFilesRowGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/RowGateway/DirectusMediaRowGateway.php', + 'Directus\\Database\\RowGateway\\DirectusUsersRowGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/RowGateway/DirectusUsersRowGateway.php', + 'Directus\\Database\\SchemaService' => __DIR__ . '/../..' . '/src/core/Directus/Database/SchemaService.php', + 'Directus\\Database\\Schema\\DataTypes' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/DataTypes.php', + 'Directus\\Database\\Schema\\Object\\AbstractObject' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Object/AbstractObject.php', + 'Directus\\Database\\Schema\\Object\\Collection' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Object/Collection.php', + 'Directus\\Database\\Schema\\Object\\Field' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Object/Field.php', + 'Directus\\Database\\Schema\\Object\\FieldRelationship' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Object/FieldRelationship.php', + 'Directus\\Database\\Schema\\SchemaFactory' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/SchemaFactory.php', + 'Directus\\Database\\Schema\\SchemaManager' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/SchemaManager.php', + 'Directus\\Database\\Schema\\Sources\\AbstractSchema' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Sources/AbstractSchema.php', + 'Directus\\Database\\Schema\\Sources\\MySQLSchema' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Sources/MySQLSchema.php', + 'Directus\\Database\\Schema\\Sources\\SQLiteSchema' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Sources/SQLiteSchema.php', + 'Directus\\Database\\Schema\\Sources\\SchemaInterface' => __DIR__ . '/../..' . '/src/core/Directus/Database/Schema/Sources/SchemaInterface.php', + 'Directus\\Database\\TableGatewayFactory' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGatewayFactory.php', + 'Directus\\Database\\TableGateway\\BaseTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/BaseTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusActivityTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusActivityTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusCollectionPresetsTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusCollectionPresetsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusCollectionsTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusCollectionsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusPermissionsTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusPermissionsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusRolesTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusRolesTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusSettingsTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusSettingsTableGateway.php', + 'Directus\\Database\\TableGateway\\DirectusUsersTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/DirectusUsersTableGateway.php', + 'Directus\\Database\\TableGateway\\RelationalTableGateway' => __DIR__ . '/../..' . '/src/core/Directus/Database/TableGateway/RelationalTableGateway.php', + 'Directus\\Embed\\EmbedManager' => __DIR__ . '/../..' . '/src/core/Directus/Embed/EmbedManager.php', + 'Directus\\Embed\\Provider\\AbstractProvider' => __DIR__ . '/../..' . '/src/core/Directus/Embed/Provider/AbstractProvider.php', + 'Directus\\Embed\\Provider\\ProviderInterface' => __DIR__ . '/../..' . '/src/core/Directus/Embed/Provider/ProviderInterface.php', + 'Directus\\Embed\\Provider\\VimeoProvider' => __DIR__ . '/../..' . '/src/core/Directus/Embed/Provider/VimeoProvider.php', + 'Directus\\Embed\\Provider\\YoutubeProvider' => __DIR__ . '/../..' . '/src/core/Directus/Embed/Provider/YoutubeProvider.php', + 'Directus\\Exception\\BadRequestException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/BadRequestException.php', + 'Directus\\Exception\\BadRequestExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/BadRequestExceptionInterface.php', + 'Directus\\Exception\\ConflictExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/ConflictExceptionInterface.php', + 'Directus\\Exception\\ErrorException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/ErrorException.php', + 'Directus\\Exception\\ErrorExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/ErrorExceptionInterface.php', + 'Directus\\Exception\\Exception' => __DIR__ . '/../..' . '/src/core/Directus/Exception/Exception.php', + 'Directus\\Exception\\ForbiddenException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/ForbiddenException.php', + 'Directus\\Exception\\ForbiddenExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/ForbiddenExceptionInterface.php', + 'Directus\\Exception\\Http\\Auth\\ForbiddenException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/Http/Auth/ForbiddenException.php', + 'Directus\\Exception\\Http\\BadRequestException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/Http/BadRequestException.php', + 'Directus\\Exception\\Http\\NotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/Http/NotFoundException.php', + 'Directus\\Exception\\MethodNotAllowedException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/MethodNotAllowedException.php', + 'Directus\\Exception\\NotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/NotFoundException.php', + 'Directus\\Exception\\NotFoundExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/NotFoundExceptionInterface.php', + 'Directus\\Exception\\RuntimeException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/RuntimeException.php', + 'Directus\\Exception\\UnauthorizedException' => __DIR__ . '/../..' . '/src/core/Directus/Exception/UnauthorizedException.php', + 'Directus\\Exception\\UnauthorizedExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/UnauthorizedExceptionInterface.php', + 'Directus\\Exception\\UnprocessableEntityExceptionInterface' => __DIR__ . '/../..' . '/src/core/Directus/Exception/UnprocessableEntityExceptionInterface.php', + 'Directus\\Filesystem\\Exception\\FailedUploadException' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Exception/FailedUploadException.php', + 'Directus\\Filesystem\\Exception\\FilesystemException' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Exception/FilesystemException.php', + 'Directus\\Filesystem\\Exception\\ForbiddenException' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Exception/ForbiddenException.php', + 'Directus\\Filesystem\\Files' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Files.php', + 'Directus\\Filesystem\\Filesystem' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Filesystem.php', + 'Directus\\Filesystem\\FilesystemFactory' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/FilesystemFactory.php', + 'Directus\\Filesystem\\Thumbnail' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Thumbnail.php', + 'Directus\\Filesystem\\Thumbnailer' => __DIR__ . '/../..' . '/src/core/Directus/Filesystem/Thumbnailer.php', + 'Directus\\Hash\\Exception\\HasherNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Exception/HasherNotFoundException.php', + 'Directus\\Hash\\HashManager' => __DIR__ . '/../..' . '/src/core/Directus/Hash/HashManager.php', + 'Directus\\Hash\\Hasher\\AbstractHashHasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/AbstractHashHasher.php', + 'Directus\\Hash\\Hasher\\BCryptHasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/BCryptHasher.php', + 'Directus\\Hash\\Hasher\\CoreHasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/CoreHasher.php', + 'Directus\\Hash\\Hasher\\HasherInterface' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/HasherInterface.php', + 'Directus\\Hash\\Hasher\\MD5Hasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/MD5Hasher.php', + 'Directus\\Hash\\Hasher\\Sha1Hasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/Sha1Hasher.php', + 'Directus\\Hash\\Hasher\\Sha224Hasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/Sha224Hasher.php', + 'Directus\\Hash\\Hasher\\Sha256Hasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/Sha256Hasher.php', + 'Directus\\Hash\\Hasher\\Sha384Hasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/Sha384Hasher.php', + 'Directus\\Hash\\Hasher\\Sha512Hasher' => __DIR__ . '/../..' . '/src/core/Directus/Hash/Hasher/Sha512Hasher.php', + 'Directus\\Hook\\Emitter' => __DIR__ . '/../..' . '/src/core/Directus/Hook/Emitter.php', + 'Directus\\Hook\\HookInterface' => __DIR__ . '/../..' . '/src/core/Directus/Hook/HookInterface.php', + 'Directus\\Hook\\Payload' => __DIR__ . '/../..' . '/src/core/Directus/Hook/Payload.php', + 'Directus\\Mail\\Exception\\InvalidTransportException' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Exception/InvalidTransportException.php', + 'Directus\\Mail\\Exception\\InvalidTransportObjectException' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Exception/InvalidTransportObjectException.php', + 'Directus\\Mail\\Exception\\TransportNotFoundException' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Exception/TransportNotFoundException.php', + 'Directus\\Mail\\Mailer' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Mailer.php', + 'Directus\\Mail\\Message' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Message.php', + 'Directus\\Mail\\TransportManager' => __DIR__ . '/../..' . '/src/core/Directus/Mail/TransportManager.php', + 'Directus\\Mail\\Transports\\AbstractTransport' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Transports/AbstractTransport.php', + 'Directus\\Mail\\Transports\\SendMailTransport' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Transports/SendMailTransport.php', + 'Directus\\Mail\\Transports\\SimpleFileTransport' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Transports/SimpleFileTransport.php', + 'Directus\\Mail\\Transports\\SmtpTransport' => __DIR__ . '/../..' . '/src/core/Directus/Mail/Transports/SmtpTransport.php', + 'Directus\\Permissions\\Acl' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Acl.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionAlterException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionAlterException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionCreateException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionCreateException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionDeleteException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionDeleteException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionReadException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionReadException.php', + 'Directus\\Permissions\\Exception\\ForbiddenCollectionUpdateException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenCollectionUpdateException.php', + 'Directus\\Permissions\\Exception\\ForbiddenException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenException.php', + 'Directus\\Permissions\\Exception\\ForbiddenFieldReadException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenFieldReadException.php', + 'Directus\\Permissions\\Exception\\ForbiddenFieldWriteException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/ForbiddenFieldWriteException.php', + 'Directus\\Permissions\\Exception\\PermissionException' => __DIR__ . '/../..' . '/src/core/Directus/Permissions/Exception/PermissionException.php', + 'Directus\\Services\\AbstractExtensionsController' => __DIR__ . '/../..' . '/src/core/Directus/Services/AbstractExtensionsController.php', + 'Directus\\Services\\AbstractService' => __DIR__ . '/../..' . '/src/core/Directus/Services/AbstractService.php', + 'Directus\\Services\\ActivityService' => __DIR__ . '/../..' . '/src/core/Directus/Services/ActivityService.php', + 'Directus\\Services\\AuthService' => __DIR__ . '/../..' . '/src/core/Directus/Services/AuthService.php', + 'Directus\\Services\\CollectionPresetsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/CollectionPresetsService.php', + 'Directus\\Services\\FilesServices' => __DIR__ . '/../..' . '/src/core/Directus/Services/FilesServices.php', + 'Directus\\Services\\InterfacesService' => __DIR__ . '/../..' . '/src/core/Directus/Services/InterfacesService.php', + 'Directus\\Services\\ItemsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/ItemsService.php', + 'Directus\\Services\\ListingsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/ListingsService.php', + 'Directus\\Services\\PagesService' => __DIR__ . '/../..' . '/src/core/Directus/Services/PagesService.php', + 'Directus\\Services\\PermissionsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/PermissionsService.php', + 'Directus\\Services\\RelationsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/RelationsService.php', + 'Directus\\Services\\RevisionsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/RevisionsService.php', + 'Directus\\Services\\RolesService' => __DIR__ . '/../..' . '/src/core/Directus/Services/RolesService.php', + 'Directus\\Services\\ScimService' => __DIR__ . '/../..' . '/src/core/Directus/Services/ScimService.php', + 'Directus\\Services\\ServerService' => __DIR__ . '/../..' . '/src/core/Directus/Services/ServerService.php', + 'Directus\\Services\\SettingsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/SettingsService.php', + 'Directus\\Services\\TablesService' => __DIR__ . '/../..' . '/src/core/Directus/Services/TablesService.php', + 'Directus\\Services\\UsersService' => __DIR__ . '/../..' . '/src/core/Directus/Services/UsersService.php', + 'Directus\\Services\\UtilsService' => __DIR__ . '/../..' . '/src/core/Directus/Services/UtilsService.php', + 'Directus\\Session\\Session' => __DIR__ . '/../..' . '/src/core/Directus/Session/Session.php', + 'Directus\\Session\\Storage\\ArraySessionStorage' => __DIR__ . '/../..' . '/src/core/Directus/Session/Storage/ArraySessionStorage.php', + 'Directus\\Session\\Storage\\NativeSessionStorage' => __DIR__ . '/../..' . '/src/core/Directus/Session/Storage/NativeSessionStorage.php', + 'Directus\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/../..' . '/src/core/Directus/Session/Storage/SessionStorageInterface.php', + 'Directus\\Slim\\Middleware' => __DIR__ . '/../..' . '/src/core/Directus/Application/Http/Middleware/Middleware.php', + 'Directus\\Util\\ArrayUtils' => __DIR__ . '/../..' . '/src/core/Directus/Util/ArrayUtils.php', + 'Directus\\Util\\DateTimeUtils' => __DIR__ . '/../..' . '/src/core/Directus/Util/DateTimeUtils.php', + 'Directus\\Util\\Formatting' => __DIR__ . '/../..' . '/src/core/Directus/Util/Formatting.php', + 'Directus\\Util\\Git' => __DIR__ . '/../..' . '/src/core/Directus/Util/Git.php', + 'Directus\\Util\\Installation\\InstallerUtils' => __DIR__ . '/../..' . '/src/core/Directus/Util/Installation/InstallerUtils.php', + 'Directus\\Util\\JWTUtils' => __DIR__ . '/../..' . '/src/core/Directus/Util/JWTUtils.php', + 'Directus\\Util\\SchemaUtils' => __DIR__ . '/../..' . '/src/core/Directus/Util/SchemaUtils.php', + 'Directus\\Util\\StringUtils' => __DIR__ . '/../..' . '/src/core/Directus/Util/StringUtils.php', + 'Directus\\Validator\\Constraints\\Required' => __DIR__ . '/../..' . '/src/core/Directus/Validator/Constraints/Required.php', + 'Directus\\Validator\\Constraints\\RequiredValidator' => __DIR__ . '/../..' . '/src/core/Directus/Validator/Constraints/RequiredValidator.php', + 'Directus\\Validator\\Exception\\InvalidRequestException' => __DIR__ . '/../..' . '/src/core/Directus/Validator/Exception/InvalidRequestException.php', + 'Directus\\Validator\\Exception\\UnknownConstraintException' => __DIR__ . '/../..' . '/src/core/Directus/Validator/Exception/UnknownConstraintException.php', + 'Directus\\Validator\\Validator' => __DIR__ . '/../..' . '/src/core/Directus/Validator/Validator.php', + 'Doctrine\\Common\\Cache\\ApcCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php', + 'Doctrine\\Common\\Cache\\ApcuCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php', + 'Doctrine\\Common\\Cache\\ArrayCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php', + 'Doctrine\\Common\\Cache\\Cache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php', + 'Doctrine\\Common\\Cache\\CacheProvider' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php', + 'Doctrine\\Common\\Cache\\ChainCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php', + 'Doctrine\\Common\\Cache\\ClearableCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php', + 'Doctrine\\Common\\Cache\\CouchbaseCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php', + 'Doctrine\\Common\\Cache\\ExtMongoDBCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php', + 'Doctrine\\Common\\Cache\\FileCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php', + 'Doctrine\\Common\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php', + 'Doctrine\\Common\\Cache\\FlushableCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php', + 'Doctrine\\Common\\Cache\\LegacyMongoDBCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/LegacyMongoDBCache.php', + 'Doctrine\\Common\\Cache\\MemcacheCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php', + 'Doctrine\\Common\\Cache\\MemcachedCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php', + 'Doctrine\\Common\\Cache\\MongoDBCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php', + 'Doctrine\\Common\\Cache\\MultiDeleteCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php', + 'Doctrine\\Common\\Cache\\MultiGetCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php', + 'Doctrine\\Common\\Cache\\MultiOperationCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiOperationCache.php', + 'Doctrine\\Common\\Cache\\MultiPutCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php', + 'Doctrine\\Common\\Cache\\PhpFileCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php', + 'Doctrine\\Common\\Cache\\PredisCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php', + 'Doctrine\\Common\\Cache\\RedisCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php', + 'Doctrine\\Common\\Cache\\RiakCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php', + 'Doctrine\\Common\\Cache\\SQLite3Cache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php', + 'Doctrine\\Common\\Cache\\Version' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/Version.php', + 'Doctrine\\Common\\Cache\\VoidCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php', + 'Doctrine\\Common\\Cache\\WinCacheCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php', + 'Doctrine\\Common\\Cache\\XcacheCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php', + 'Doctrine\\Common\\Cache\\ZendDataCache' => __DIR__ . '/..' . '/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php', + 'Doctrine\\Instantiator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php', + 'Doctrine\\Instantiator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php', + 'Doctrine\\Instantiator\\Exception\\UnexpectedValueException' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php', + 'Doctrine\\Instantiator\\Instantiator' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php', + 'Doctrine\\Instantiator\\InstantiatorInterface' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php', + 'FastRoute\\BadRouteException' => __DIR__ . '/..' . '/nikic/fast-route/src/BadRouteException.php', + 'FastRoute\\DataGenerator' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator.php', + 'FastRoute\\DataGenerator\\CharCountBased' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator/CharCountBased.php', + 'FastRoute\\DataGenerator\\GroupCountBased' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator/GroupCountBased.php', + 'FastRoute\\DataGenerator\\GroupPosBased' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator/GroupPosBased.php', + 'FastRoute\\DataGenerator\\MarkBased' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator/MarkBased.php', + 'FastRoute\\DataGenerator\\RegexBasedAbstract' => __DIR__ . '/..' . '/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php', + 'FastRoute\\Dispatcher' => __DIR__ . '/..' . '/nikic/fast-route/src/Dispatcher.php', + 'FastRoute\\Dispatcher\\CharCountBased' => __DIR__ . '/..' . '/nikic/fast-route/src/Dispatcher/CharCountBased.php', + 'FastRoute\\Dispatcher\\GroupCountBased' => __DIR__ . '/..' . '/nikic/fast-route/src/Dispatcher/GroupCountBased.php', + 'FastRoute\\Dispatcher\\GroupPosBased' => __DIR__ . '/..' . '/nikic/fast-route/src/Dispatcher/GroupPosBased.php', + 'FastRoute\\Dispatcher\\MarkBased' => __DIR__ . '/..' . '/nikic/fast-route/src/Dispatcher/MarkBased.php', + 'FastRoute\\Dispatcher\\RegexBasedAbstract' => __DIR__ . '/..' . '/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php', + 'FastRoute\\Route' => __DIR__ . '/..' . '/nikic/fast-route/src/Route.php', + 'FastRoute\\RouteCollector' => __DIR__ . '/..' . '/nikic/fast-route/src/RouteCollector.php', + 'FastRoute\\RouteParser' => __DIR__ . '/..' . '/nikic/fast-route/src/RouteParser.php', + 'FastRoute\\RouteParser\\Std' => __DIR__ . '/..' . '/nikic/fast-route/src/RouteParser/Std.php', + 'File_Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php', + 'File_Iterator_Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php', + 'File_Iterator_Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php', + 'Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php', + 'Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php', + 'Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php', + 'Firebase\\JWT\\SignatureInvalidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/SignatureInvalidException.php', + 'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\SeekException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/SeekException.php', + 'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\EachPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Promise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\RedirectMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\UriTemplate' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'Interop\\Container\\ContainerInterface' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/ContainerInterface.php', + 'Interop\\Container\\Exception\\ContainerException' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php', + 'Interop\\Container\\Exception\\NotFoundException' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php', + 'Intervention\\Image\\AbstractColor' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/AbstractColor.php', + 'Intervention\\Image\\AbstractDecoder' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/AbstractDecoder.php', + 'Intervention\\Image\\AbstractDriver' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/AbstractDriver.php', + 'Intervention\\Image\\AbstractEncoder' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/AbstractEncoder.php', + 'Intervention\\Image\\AbstractFont' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/AbstractFont.php', + 'Intervention\\Image\\AbstractShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/AbstractShape.php', + 'Intervention\\Image\\Commands\\AbstractCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php', + 'Intervention\\Image\\Commands\\Argument' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/Argument.php', + 'Intervention\\Image\\Commands\\ChecksumCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php', + 'Intervention\\Image\\Commands\\CircleCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/CircleCommand.php', + 'Intervention\\Image\\Commands\\EllipseCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php', + 'Intervention\\Image\\Commands\\ExifCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/ExifCommand.php', + 'Intervention\\Image\\Commands\\IptcCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/IptcCommand.php', + 'Intervention\\Image\\Commands\\LineCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/LineCommand.php', + 'Intervention\\Image\\Commands\\OrientateCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php', + 'Intervention\\Image\\Commands\\PolygonCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php', + 'Intervention\\Image\\Commands\\PsrResponseCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php', + 'Intervention\\Image\\Commands\\RectangleCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php', + 'Intervention\\Image\\Commands\\ResponseCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php', + 'Intervention\\Image\\Commands\\StreamCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/StreamCommand.php', + 'Intervention\\Image\\Commands\\TextCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Commands/TextCommand.php', + 'Intervention\\Image\\Constraint' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Constraint.php', + 'Intervention\\Image\\Exception\\ImageException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/ImageException.php', + 'Intervention\\Image\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/InvalidArgumentException.php', + 'Intervention\\Image\\Exception\\MissingDependencyException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/MissingDependencyException.php', + 'Intervention\\Image\\Exception\\NotFoundException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/NotFoundException.php', + 'Intervention\\Image\\Exception\\NotReadableException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/NotReadableException.php', + 'Intervention\\Image\\Exception\\NotSupportedException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/NotSupportedException.php', + 'Intervention\\Image\\Exception\\NotWritableException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/NotWritableException.php', + 'Intervention\\Image\\Exception\\RuntimeException' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Exception/RuntimeException.php', + 'Intervention\\Image\\Facades\\Image' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Facades/Image.php', + 'Intervention\\Image\\File' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/File.php', + 'Intervention\\Image\\Filters\\DemoFilter' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Filters/DemoFilter.php', + 'Intervention\\Image\\Filters\\FilterInterface' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Filters/FilterInterface.php', + 'Intervention\\Image\\Gd\\Color' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Color.php', + 'Intervention\\Image\\Gd\\Commands\\BackupCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php', + 'Intervention\\Image\\Gd\\Commands\\BlurCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php', + 'Intervention\\Image\\Gd\\Commands\\BrightnessCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ColorizeCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ContrastCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php', + 'Intervention\\Image\\Gd\\Commands\\CropCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php', + 'Intervention\\Image\\Gd\\Commands\\DestroyCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php', + 'Intervention\\Image\\Gd\\Commands\\FillCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php', + 'Intervention\\Image\\Gd\\Commands\\FitCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php', + 'Intervention\\Image\\Gd\\Commands\\FlipCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php', + 'Intervention\\Image\\Gd\\Commands\\GammaCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php', + 'Intervention\\Image\\Gd\\Commands\\GetSizeCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php', + 'Intervention\\Image\\Gd\\Commands\\GreyscaleCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php', + 'Intervention\\Image\\Gd\\Commands\\HeightenCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php', + 'Intervention\\Image\\Gd\\Commands\\InsertCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php', + 'Intervention\\Image\\Gd\\Commands\\InterlaceCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php', + 'Intervention\\Image\\Gd\\Commands\\InvertCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php', + 'Intervention\\Image\\Gd\\Commands\\LimitColorsCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php', + 'Intervention\\Image\\Gd\\Commands\\MaskCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php', + 'Intervention\\Image\\Gd\\Commands\\OpacityCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php', + 'Intervention\\Image\\Gd\\Commands\\PickColorCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php', + 'Intervention\\Image\\Gd\\Commands\\PixelCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php', + 'Intervention\\Image\\Gd\\Commands\\PixelateCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ResetCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ResizeCanvasCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php', + 'Intervention\\Image\\Gd\\Commands\\ResizeCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php', + 'Intervention\\Image\\Gd\\Commands\\RotateCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php', + 'Intervention\\Image\\Gd\\Commands\\SharpenCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php', + 'Intervention\\Image\\Gd\\Commands\\TrimCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php', + 'Intervention\\Image\\Gd\\Commands\\WidenCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php', + 'Intervention\\Image\\Gd\\Decoder' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Decoder.php', + 'Intervention\\Image\\Gd\\Driver' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Driver.php', + 'Intervention\\Image\\Gd\\Encoder' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Encoder.php', + 'Intervention\\Image\\Gd\\Font' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Font.php', + 'Intervention\\Image\\Gd\\Shapes\\CircleShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php', + 'Intervention\\Image\\Gd\\Shapes\\EllipseShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php', + 'Intervention\\Image\\Gd\\Shapes\\LineShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php', + 'Intervention\\Image\\Gd\\Shapes\\PolygonShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php', + 'Intervention\\Image\\Gd\\Shapes\\RectangleShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php', + 'Intervention\\Image\\Image' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Image.php', + 'Intervention\\Image\\ImageManager' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageManager.php', + 'Intervention\\Image\\ImageManagerStatic' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageManagerStatic.php', + 'Intervention\\Image\\ImageServiceProvider' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageServiceProvider.php', + 'Intervention\\Image\\ImageServiceProviderLaravel4' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php', + 'Intervention\\Image\\ImageServiceProviderLaravel5' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel5.php', + 'Intervention\\Image\\ImageServiceProviderLeague' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php', + 'Intervention\\Image\\ImageServiceProviderLumen' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php', + 'Intervention\\Image\\Imagick\\Color' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Color.php', + 'Intervention\\Image\\Imagick\\Commands\\BackupCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\BlurCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\BrightnessCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ColorizeCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ContrastCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\CropCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\DestroyCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ExifCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\FillCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\FitCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\FlipCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\GammaCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\GetSizeCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\GreyscaleCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\HeightenCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\InsertCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\InterlaceCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\InvertCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\LimitColorsCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\MaskCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\OpacityCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\PickColorCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\PixelCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\PixelateCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ResetCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ResizeCanvasCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\ResizeCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\RotateCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\SharpenCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\TrimCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php', + 'Intervention\\Image\\Imagick\\Commands\\WidenCommand' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php', + 'Intervention\\Image\\Imagick\\Decoder' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Decoder.php', + 'Intervention\\Image\\Imagick\\Driver' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Driver.php', + 'Intervention\\Image\\Imagick\\Encoder' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Encoder.php', + 'Intervention\\Image\\Imagick\\Font' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Font.php', + 'Intervention\\Image\\Imagick\\Shapes\\CircleShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\EllipseShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\LineShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\PolygonShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php', + 'Intervention\\Image\\Imagick\\Shapes\\RectangleShape' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php', + 'Intervention\\Image\\Point' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Point.php', + 'Intervention\\Image\\Response' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Response.php', + 'Intervention\\Image\\Size' => __DIR__ . '/..' . '/intervention/image/src/Intervention/Image/Size.php', + 'League\\Flysystem\\AdapterInterface' => __DIR__ . '/..' . '/league/flysystem/src/AdapterInterface.php', + 'League\\Flysystem\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/AbstractAdapter.php', + 'League\\Flysystem\\Adapter\\AbstractFtpAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/AbstractFtpAdapter.php', + 'League\\Flysystem\\Adapter\\CanOverwriteFiles' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/CanOverwriteFiles.php', + 'League\\Flysystem\\Adapter\\Ftp' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Ftp.php', + 'League\\Flysystem\\Adapter\\Ftpd' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Ftpd.php', + 'League\\Flysystem\\Adapter\\Local' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Local.php', + 'League\\Flysystem\\Adapter\\NullAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/NullAdapter.php', + 'League\\Flysystem\\Adapter\\Polyfill\\NotSupportingVisibilityTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedCopyTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedReadingTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedWritingTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php', + 'League\\Flysystem\\Adapter\\SynologyFtp' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/SynologyFtp.php', + 'League\\Flysystem\\Config' => __DIR__ . '/..' . '/league/flysystem/src/Config.php', + 'League\\Flysystem\\ConfigAwareTrait' => __DIR__ . '/..' . '/league/flysystem/src/ConfigAwareTrait.php', + 'League\\Flysystem\\Directory' => __DIR__ . '/..' . '/league/flysystem/src/Directory.php', + 'League\\Flysystem\\Exception' => __DIR__ . '/..' . '/league/flysystem/src/Exception.php', + 'League\\Flysystem\\File' => __DIR__ . '/..' . '/league/flysystem/src/File.php', + 'League\\Flysystem\\FileExistsException' => __DIR__ . '/..' . '/league/flysystem/src/FileExistsException.php', + 'League\\Flysystem\\FileNotFoundException' => __DIR__ . '/..' . '/league/flysystem/src/FileNotFoundException.php', + 'League\\Flysystem\\Filesystem' => __DIR__ . '/..' . '/league/flysystem/src/Filesystem.php', + 'League\\Flysystem\\FilesystemInterface' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemInterface.php', + 'League\\Flysystem\\FilesystemNotFoundException' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemNotFoundException.php', + 'League\\Flysystem\\Handler' => __DIR__ . '/..' . '/league/flysystem/src/Handler.php', + 'League\\Flysystem\\MountManager' => __DIR__ . '/..' . '/league/flysystem/src/MountManager.php', + 'League\\Flysystem\\NotSupportedException' => __DIR__ . '/..' . '/league/flysystem/src/NotSupportedException.php', + 'League\\Flysystem\\PluginInterface' => __DIR__ . '/..' . '/league/flysystem/src/PluginInterface.php', + 'League\\Flysystem\\Plugin\\AbstractPlugin' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/AbstractPlugin.php', + 'League\\Flysystem\\Plugin\\EmptyDir' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/EmptyDir.php', + 'League\\Flysystem\\Plugin\\ForcedCopy' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ForcedCopy.php', + 'League\\Flysystem\\Plugin\\ForcedRename' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ForcedRename.php', + 'League\\Flysystem\\Plugin\\GetWithMetadata' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/GetWithMetadata.php', + 'League\\Flysystem\\Plugin\\ListFiles' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ListFiles.php', + 'League\\Flysystem\\Plugin\\ListPaths' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ListPaths.php', + 'League\\Flysystem\\Plugin\\ListWith' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ListWith.php', + 'League\\Flysystem\\Plugin\\PluggableTrait' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/PluggableTrait.php', + 'League\\Flysystem\\Plugin\\PluginNotFoundException' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/PluginNotFoundException.php', + 'League\\Flysystem\\ReadInterface' => __DIR__ . '/..' . '/league/flysystem/src/ReadInterface.php', + 'League\\Flysystem\\RootViolationException' => __DIR__ . '/..' . '/league/flysystem/src/RootViolationException.php', + 'League\\Flysystem\\SafeStorage' => __DIR__ . '/..' . '/league/flysystem/src/SafeStorage.php', + 'League\\Flysystem\\UnreadableFileException' => __DIR__ . '/..' . '/league/flysystem/src/UnreadableFileException.php', + 'League\\Flysystem\\Util' => __DIR__ . '/..' . '/league/flysystem/src/Util.php', + 'League\\Flysystem\\Util\\ContentListingFormatter' => __DIR__ . '/..' . '/league/flysystem/src/Util/ContentListingFormatter.php', + 'League\\Flysystem\\Util\\MimeType' => __DIR__ . '/..' . '/league/flysystem/src/Util/MimeType.php', + 'League\\Flysystem\\Util\\StreamHasher' => __DIR__ . '/..' . '/league/flysystem/src/Util/StreamHasher.php', + 'League\\OAuth1\\Client\\Credentials\\ClientCredentials' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/ClientCredentials.php', + 'League\\OAuth1\\Client\\Credentials\\ClientCredentialsInterface' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/ClientCredentialsInterface.php', + 'League\\OAuth1\\Client\\Credentials\\Credentials' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/Credentials.php', + 'League\\OAuth1\\Client\\Credentials\\CredentialsException' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/CredentialsException.php', + 'League\\OAuth1\\Client\\Credentials\\CredentialsInterface' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/CredentialsInterface.php', + 'League\\OAuth1\\Client\\Credentials\\TemporaryCredentials' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/TemporaryCredentials.php', + 'League\\OAuth1\\Client\\Credentials\\TokenCredentials' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Credentials/TokenCredentials.php', + 'League\\OAuth1\\Client\\Server\\Bitbucket' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Bitbucket.php', + 'League\\OAuth1\\Client\\Server\\Magento' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Magento.php', + 'League\\OAuth1\\Client\\Server\\Server' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Server.php', + 'League\\OAuth1\\Client\\Server\\Trello' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Trello.php', + 'League\\OAuth1\\Client\\Server\\Tumblr' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Tumblr.php', + 'League\\OAuth1\\Client\\Server\\Twitter' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Twitter.php', + 'League\\OAuth1\\Client\\Server\\User' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/User.php', + 'League\\OAuth1\\Client\\Server\\Uservoice' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Uservoice.php', + 'League\\OAuth1\\Client\\Server\\Xing' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Server/Xing.php', + 'League\\OAuth1\\Client\\Signature\\HmacSha1Signature' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Signature/HmacSha1Signature.php', + 'League\\OAuth1\\Client\\Signature\\PlainTextSignature' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Signature/PlainTextSignature.php', + 'League\\OAuth1\\Client\\Signature\\Signature' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Signature/Signature.php', + 'League\\OAuth1\\Client\\Signature\\SignatureInterface' => __DIR__ . '/..' . '/league/oauth1-client/src/Client/Signature/SignatureInterface.php', + 'League\\OAuth2\\Client\\Exception\\HostedDomainException' => __DIR__ . '/..' . '/league/oauth2-google/src/Exception/HostedDomainException.php', + 'League\\OAuth2\\Client\\Grant\\AbstractGrant' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/AbstractGrant.php', + 'League\\OAuth2\\Client\\Grant\\AuthorizationCode' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/AuthorizationCode.php', + 'League\\OAuth2\\Client\\Grant\\ClientCredentials' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/ClientCredentials.php', + 'League\\OAuth2\\Client\\Grant\\Exception\\InvalidGrantException' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php', + 'League\\OAuth2\\Client\\Grant\\FbExchangeToken' => __DIR__ . '/..' . '/league/oauth2-facebook/src/Grant/FbExchangeToken.php', + 'League\\OAuth2\\Client\\Grant\\GrantFactory' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/GrantFactory.php', + 'League\\OAuth2\\Client\\Grant\\Password' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/Password.php', + 'League\\OAuth2\\Client\\Grant\\RefreshToken' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/RefreshToken.php', + 'League\\OAuth2\\Client\\Provider\\AbstractProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/AbstractProvider.php', + 'League\\OAuth2\\Client\\Provider\\AppSecretProof' => __DIR__ . '/..' . '/league/oauth2-facebook/src/Provider/AppSecretProof.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\FacebookProviderException' => __DIR__ . '/..' . '/league/oauth2-facebook/src/Provider/Exception/FacebookProviderException.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\GithubIdentityProviderException' => __DIR__ . '/..' . '/league/oauth2-github/src/Provider/Exception/GithubIdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\Facebook' => __DIR__ . '/..' . '/league/oauth2-facebook/src/Provider/Facebook.php', + 'League\\OAuth2\\Client\\Provider\\FacebookUser' => __DIR__ . '/..' . '/league/oauth2-facebook/src/Provider/FacebookUser.php', + 'League\\OAuth2\\Client\\Provider\\GenericProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/GenericProvider.php', + 'League\\OAuth2\\Client\\Provider\\GenericResourceOwner' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/GenericResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Github' => __DIR__ . '/..' . '/league/oauth2-github/src/Provider/Github.php', + 'League\\OAuth2\\Client\\Provider\\GithubResourceOwner' => __DIR__ . '/..' . '/league/oauth2-github/src/Provider/GithubResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Google' => __DIR__ . '/..' . '/league/oauth2-google/src/Provider/Google.php', + 'League\\OAuth2\\Client\\Provider\\GoogleUser' => __DIR__ . '/..' . '/league/oauth2-google/src/Provider/GoogleUser.php', + 'League\\OAuth2\\Client\\Provider\\ResourceOwnerInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/ResourceOwnerInterface.php', + 'League\\OAuth2\\Client\\Token\\AccessToken' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/AccessToken.php', + 'League\\OAuth2\\Client\\Tool\\ArrayAccessorTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/ArrayAccessorTrait.php', + 'League\\OAuth2\\Client\\Tool\\BearerAuthorizationTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\MacAuthorizationTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/MacAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\ProviderRedirectTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/ProviderRedirectTrait.php', + 'League\\OAuth2\\Client\\Tool\\QueryBuilderTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/QueryBuilderTrait.php', + 'League\\OAuth2\\Client\\Tool\\RequestFactory' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/RequestFactory.php', + 'League\\OAuth2\\Client\\Tool\\RequiredParameterTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/RequiredParameterTrait.php', + 'Monolog\\ErrorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ErrorHandler.php', + 'Monolog\\Formatter\\ChromePHPFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', + 'Monolog\\Formatter\\ElasticaFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php', + 'Monolog\\Formatter\\FlowdockFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php', + 'Monolog\\Formatter\\FluentdFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php', + 'Monolog\\Formatter\\FormatterInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', + 'Monolog\\Formatter\\GelfMessageFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', + 'Monolog\\Formatter\\HtmlFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php', + 'Monolog\\Formatter\\JsonFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', + 'Monolog\\Formatter\\LineFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', + 'Monolog\\Formatter\\LogglyFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php', + 'Monolog\\Formatter\\LogstashFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', + 'Monolog\\Formatter\\MongoDBFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php', + 'Monolog\\Formatter\\NormalizerFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', + 'Monolog\\Formatter\\ScalarFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php', + 'Monolog\\Formatter\\WildfireFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', + 'Monolog\\Handler\\AbstractHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', + 'Monolog\\Handler\\AbstractProcessingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', + 'Monolog\\Handler\\AbstractSyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php', + 'Monolog\\Handler\\AmqpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', + 'Monolog\\Handler\\BrowserConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php', + 'Monolog\\Handler\\BufferHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', + 'Monolog\\Handler\\ChromePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', + 'Monolog\\Handler\\CouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', + 'Monolog\\Handler\\CubeHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', + 'Monolog\\Handler\\Curl\\Util' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php', + 'Monolog\\Handler\\DeduplicationHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php', + 'Monolog\\Handler\\DoctrineCouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', + 'Monolog\\Handler\\DynamoDbHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php', + 'Monolog\\Handler\\ElasticSearchHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php', + 'Monolog\\Handler\\ErrorLogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', + 'Monolog\\Handler\\FilterHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php', + 'Monolog\\Handler\\FingersCrossedHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', + 'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', + 'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', + 'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', + 'Monolog\\Handler\\FirePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', + 'Monolog\\Handler\\FleepHookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php', + 'Monolog\\Handler\\FlowdockHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php', + 'Monolog\\Handler\\GelfHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', + 'Monolog\\Handler\\GroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', + 'Monolog\\Handler\\HandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', + 'Monolog\\Handler\\HandlerWrapper' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php', + 'Monolog\\Handler\\HipChatHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HipChatHandler.php', + 'Monolog\\Handler\\IFTTTHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php', + 'Monolog\\Handler\\LogEntriesHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php', + 'Monolog\\Handler\\LogglyHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php', + 'Monolog\\Handler\\MailHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', + 'Monolog\\Handler\\MandrillHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php', + 'Monolog\\Handler\\MissingExtensionException' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', + 'Monolog\\Handler\\MongoDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', + 'Monolog\\Handler\\NativeMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', + 'Monolog\\Handler\\NewRelicHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', + 'Monolog\\Handler\\NullHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', + 'Monolog\\Handler\\PHPConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php', + 'Monolog\\Handler\\PsrHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php', + 'Monolog\\Handler\\PushoverHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', + 'Monolog\\Handler\\RavenHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RavenHandler.php', + 'Monolog\\Handler\\RedisHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', + 'Monolog\\Handler\\RollbarHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php', + 'Monolog\\Handler\\RotatingFileHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', + 'Monolog\\Handler\\SamplingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', + 'Monolog\\Handler\\SlackHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', + 'Monolog\\Handler\\SlackWebhookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', + 'Monolog\\Handler\\Slack\\SlackRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', + 'Monolog\\Handler\\SlackbotHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php', + 'Monolog\\Handler\\SocketHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', + 'Monolog\\Handler\\StreamHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', + 'Monolog\\Handler\\SwiftMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', + 'Monolog\\Handler\\SyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', + 'Monolog\\Handler\\SyslogUdpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php', + 'Monolog\\Handler\\SyslogUdp\\UdpSocket' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php', + 'Monolog\\Handler\\TestHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', + 'Monolog\\Handler\\WhatFailureGroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php', + 'Monolog\\Handler\\ZendMonitorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', + 'Monolog\\Logger' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Logger.php', + 'Monolog\\Processor\\GitProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php', + 'Monolog\\Processor\\IntrospectionProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', + 'Monolog\\Processor\\MemoryPeakUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', + 'Monolog\\Processor\\MemoryProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', + 'Monolog\\Processor\\MemoryUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\MercurialProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', + 'Monolog\\Processor\\ProcessIdProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', + 'Monolog\\Processor\\PsrLogMessageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', + 'Monolog\\Processor\\TagProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', + 'Monolog\\Processor\\UidProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', + 'Monolog\\Processor\\WebProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', + 'Monolog\\Registry' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Registry.php', + 'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/Assert.php', + 'PHPUnit\\Framework\\AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/AssertionFailedError.php', + 'PHPUnit\\Framework\\BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php', + 'PHPUnit\\Framework\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/Test.php', + 'PHPUnit\\Framework\\TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestCase.php', + 'PHPUnit\\Framework\\TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestListener.php', + 'PHPUnit\\Framework\\TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestSuite.php', + 'PHPUnit_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php', + 'PHPUnit_Extensions_GroupTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/GroupTestSuite.php', + 'PHPUnit_Extensions_PhptTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/PhptTestCase.php', + 'PHPUnit_Extensions_PhptTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/PhptTestSuite.php', + 'PHPUnit_Extensions_RepeatedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/RepeatedTest.php', + 'PHPUnit_Extensions_TestDecorator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/TestDecorator.php', + 'PHPUnit_Extensions_TicketListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/TicketListener.php', + 'PHPUnit_Framework_Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php', + 'PHPUnit_Framework_AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/AssertionFailedError.php', + 'PHPUnit_Framework_BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/BaseTestListener.php', + 'PHPUnit_Framework_CodeCoverageException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CodeCoverageException.php', + 'PHPUnit_Framework_Constraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint.php', + 'PHPUnit_Framework_Constraint_And' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/And.php', + 'PHPUnit_Framework_Constraint_ArrayHasKey' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', + 'PHPUnit_Framework_Constraint_ArraySubset' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', + 'PHPUnit_Framework_Constraint_Attribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', + 'PHPUnit_Framework_Constraint_Callback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', + 'PHPUnit_Framework_Constraint_ClassHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', + 'PHPUnit_Framework_Constraint_ClassHasStaticAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', + 'PHPUnit_Framework_Constraint_Composite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', + 'PHPUnit_Framework_Constraint_Count' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Count.php', + 'PHPUnit_Framework_Constraint_DirectoryExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php', + 'PHPUnit_Framework_Constraint_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', + 'PHPUnit_Framework_Constraint_ExceptionCode' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', + 'PHPUnit_Framework_Constraint_ExceptionMessage' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', + 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php', + 'PHPUnit_Framework_Constraint_FileExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', + 'PHPUnit_Framework_Constraint_GreaterThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', + 'PHPUnit_Framework_Constraint_IsAnything' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', + 'PHPUnit_Framework_Constraint_IsEmpty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', + 'PHPUnit_Framework_Constraint_IsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', + 'PHPUnit_Framework_Constraint_IsFalse' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', + 'PHPUnit_Framework_Constraint_IsFinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php', + 'PHPUnit_Framework_Constraint_IsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', + 'PHPUnit_Framework_Constraint_IsInfinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php', + 'PHPUnit_Framework_Constraint_IsInstanceOf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', + 'PHPUnit_Framework_Constraint_IsJson' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', + 'PHPUnit_Framework_Constraint_IsNan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php', + 'PHPUnit_Framework_Constraint_IsNull' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', + 'PHPUnit_Framework_Constraint_IsReadable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php', + 'PHPUnit_Framework_Constraint_IsTrue' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', + 'PHPUnit_Framework_Constraint_IsType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', + 'PHPUnit_Framework_Constraint_IsWritable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php', + 'PHPUnit_Framework_Constraint_JsonMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', + 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php', + 'PHPUnit_Framework_Constraint_LessThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', + 'PHPUnit_Framework_Constraint_Not' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Not.php', + 'PHPUnit_Framework_Constraint_ObjectHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', + 'PHPUnit_Framework_Constraint_Or' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Or.php', + 'PHPUnit_Framework_Constraint_PCREMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php', + 'PHPUnit_Framework_Constraint_SameSize' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', + 'PHPUnit_Framework_Constraint_StringContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', + 'PHPUnit_Framework_Constraint_StringEndsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', + 'PHPUnit_Framework_Constraint_StringMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringMatches.php', + 'PHPUnit_Framework_Constraint_StringStartsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', + 'PHPUnit_Framework_Constraint_TraversableContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', + 'PHPUnit_Framework_Constraint_TraversableContainsOnly' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', + 'PHPUnit_Framework_Constraint_Xor' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Xor.php', + 'PHPUnit_Framework_CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.php', + 'PHPUnit_Framework_Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error.php', + 'PHPUnit_Framework_Error_Deprecated' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', + 'PHPUnit_Framework_Error_Notice' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Notice.php', + 'PHPUnit_Framework_Error_Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Warning.php', + 'PHPUnit_Framework_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception.php', + 'PHPUnit_Framework_ExceptionWrapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', + 'PHPUnit_Framework_ExpectationFailedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', + 'PHPUnit_Framework_IncompleteTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTest.php', + 'PHPUnit_Framework_IncompleteTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', + 'PHPUnit_Framework_IncompleteTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', + 'PHPUnit_Framework_InvalidCoversTargetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', + 'PHPUnit_Framework_MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.php', + 'PHPUnit_Framework_MockObject_BadMethodCallException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php', + 'PHPUnit_Framework_MockObject_Builder_Identity' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php', + 'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php', + 'PHPUnit_Framework_MockObject_Builder_Match' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php', + 'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php', + 'PHPUnit_Framework_MockObject_Builder_Namespace' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php', + 'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php', + 'PHPUnit_Framework_MockObject_Builder_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php', + 'PHPUnit_Framework_MockObject_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php', + 'PHPUnit_Framework_MockObject_Generator' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php', + 'PHPUnit_Framework_MockObject_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php', + 'PHPUnit_Framework_MockObject_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php', + 'PHPUnit_Framework_MockObject_Invocation_Object' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php', + 'PHPUnit_Framework_MockObject_Invocation_Static' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php', + 'PHPUnit_Framework_MockObject_Invokable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php', + 'PHPUnit_Framework_MockObject_Matcher' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php', + 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php', + 'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php', + 'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php', + 'PHPUnit_Framework_MockObject_Matcher_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php', + 'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php', + 'PHPUnit_Framework_MockObject_Matcher_MethodName' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php', + 'PHPUnit_Framework_MockObject_Matcher_Parameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php', + 'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php', + 'PHPUnit_Framework_MockObject_MockBuilder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php', + 'PHPUnit_Framework_MockObject_MockObject' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php', + 'PHPUnit_Framework_MockObject_RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php', + 'PHPUnit_Framework_MockObject_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php', + 'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php', + 'PHPUnit_Framework_MockObject_Stub_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php', + 'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php', + 'PHPUnit_Framework_MockObject_Stub_Return' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnReference' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnReference.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php', + 'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php', + 'PHPUnit_Framework_MockObject_Verifiable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php', + 'PHPUnit_Framework_OutputError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/OutputError.php', + 'PHPUnit_Framework_RiskyTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTest.php', + 'PHPUnit_Framework_RiskyTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTestError.php', + 'PHPUnit_Framework_SelfDescribing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SelfDescribing.php', + 'PHPUnit_Framework_SkippedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTest.php', + 'PHPUnit_Framework_SkippedTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', + 'PHPUnit_Framework_SkippedTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestError.php', + 'PHPUnit_Framework_SkippedTestSuiteError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', + 'PHPUnit_Framework_SyntheticError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SyntheticError.php', + 'PHPUnit_Framework_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Test.php', + 'PHPUnit_Framework_TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestCase.php', + 'PHPUnit_Framework_TestFailure' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestFailure.php', + 'PHPUnit_Framework_TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListener.php', + 'PHPUnit_Framework_TestResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestResult.php', + 'PHPUnit_Framework_TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite.php', + 'PHPUnit_Framework_TestSuite_DataProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php', + 'PHPUnit_Framework_UnintentionallyCoveredCodeError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', + 'PHPUnit_Framework_Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Warning.php', + 'PHPUnit_Framework_WarningTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/WarningTestCase.php', + 'PHPUnit_Runner_BaseTestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', + 'PHPUnit_Runner_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Exception.php', + 'PHPUnit_Runner_Filter_Factory' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Factory.php', + 'PHPUnit_Runner_Filter_GroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group.php', + 'PHPUnit_Runner_Filter_Group_Exclude' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php', + 'PHPUnit_Runner_Filter_Group_Include' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group/Include.php', + 'PHPUnit_Runner_Filter_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Test.php', + 'PHPUnit_Runner_StandardTestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', + 'PHPUnit_Runner_TestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', + 'PHPUnit_Runner_Version' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Version.php', + 'PHPUnit_TextUI_Command' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Command.php', + 'PHPUnit_TextUI_ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', + 'PHPUnit_TextUI_TestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestRunner.php', + 'PHPUnit_Util_Blacklist' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Blacklist.php', + 'PHPUnit_Util_Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Configuration.php', + 'PHPUnit_Util_ConfigurationGenerator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php', + 'PHPUnit_Util_ErrorHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ErrorHandler.php', + 'PHPUnit_Util_Fileloader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Fileloader.php', + 'PHPUnit_Util_Filesystem' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filesystem.php', + 'PHPUnit_Util_Filter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filter.php', + 'PHPUnit_Util_Getopt' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Getopt.php', + 'PHPUnit_Util_GlobalState' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/GlobalState.php', + 'PHPUnit_Util_InvalidArgumentHelper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', + 'PHPUnit_Util_Log_JSON' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JSON.php', + 'PHPUnit_Util_Log_JUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JUnit.php', + 'PHPUnit_Util_Log_TAP' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TAP.php', + 'PHPUnit_Util_Log_TeamCity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TeamCity.php', + 'PHPUnit_Util_PHP' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP.php', + 'PHPUnit_Util_PHP_Default' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/Default.php', + 'PHPUnit_Util_PHP_Windows' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/Windows.php', + 'PHPUnit_Util_Printer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Printer.php', + 'PHPUnit_Util_Regex' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Regex.php', + 'PHPUnit_Util_String' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/String.php', + 'PHPUnit_Util_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Test.php', + 'PHPUnit_Util_TestDox_NamePrettifier' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', + 'PHPUnit_Util_TestDox_ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', + 'PHPUnit_Util_TestDox_ResultPrinter_HTML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php', + 'PHPUnit_Util_TestDox_ResultPrinter_Text' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php', + 'PHPUnit_Util_TestDox_ResultPrinter_XML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/XML.php', + 'PHPUnit_Util_TestSuiteIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestSuiteIterator.php', + 'PHPUnit_Util_Type' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Type.php', + 'PHPUnit_Util_XML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XML.php', + 'PHP_Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php', + 'PHP_Token' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_TokenWithScope' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_TokenWithScopeAndVisibility' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ABSTRACT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AMPERSAND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AND_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ARRAY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ARRAY_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ASYNC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_AWAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BACKTICK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BAD_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BOOLEAN_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BOOLEAN_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BOOL_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_BREAK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CALLABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CARET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CASE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CATCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLASS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLASS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLASS_NAME_CONSTANT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLONE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CLOSE_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COALESCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COMMA' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_COMPILER_HALT_OFFSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONCAT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONSTANT_ENCAPSED_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CONTINUE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_CURLY_OPEN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DEC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DEFAULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DIR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DIV' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DIV_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOC_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOLLAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_DOUBLE_QUOTES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ELLIPSIS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ELSE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ELSEIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EMPTY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENCAPSED_AND_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDDECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDFOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDFOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDSWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENDWHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_END_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ENUM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EQUALS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EVAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EXCLAMATION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EXIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_EXTENDS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FINAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FINALLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_FUNC_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_GLOBAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_GOTO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_HALT_COMPILER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IMPLEMENTS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INCLUDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INCLUDE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INLINE_HTML' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INSTANCEOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INSTEADOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INTERFACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_INT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ISSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_GREATER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_NOT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_NOT_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_IS_SMALLER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_Includes' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_JOIN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LAMBDA_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LAMBDA_CP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LAMBDA_OP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LINE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LIST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LOGICAL_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LOGICAL_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LOGICAL_XOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_METHOD_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MINUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MINUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MOD_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_MUL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NAMESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NEW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NS_SEPARATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_NUM_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OBJECT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_ONUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OPEN_TAG_WITH_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PERCENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PIPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PLUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PLUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_POW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_POW_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PRINT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PRIVATE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PROTECTED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_PUBLIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_QUESTION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_REQUIRE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_REQUIRE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_RETURN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SEMICOLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SHAPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SPACESHIP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_START_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STATIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STRING_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_STRING_VARNAME' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SUPER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_SWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_Stream' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream.php', + 'PHP_Token_Stream_CachingFactory' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', + 'PHP_Token_THROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TILDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TRAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TRAIT_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TRY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TYPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TYPELIST_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_TYPELIST_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_UNSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_UNSET_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_USE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_USE_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_VAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_VARIABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_WHERE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_WHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_ATTRIBUTE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_CATEGORY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_CATEGORY_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_CHILDREN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_REQUIRED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_TAG_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_TAG_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XHP_TEXT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_XOR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_YIELD' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'PHP_Token_YIELD_FROM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', + 'Phinx\\Config\\Config' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/Config.php', + 'Phinx\\Config\\ConfigInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php', + 'Phinx\\Config\\NamespaceAwareInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php', + 'Phinx\\Config\\NamespaceAwareTrait' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php', + 'Phinx\\Console\\Command\\AbstractCommand' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php', + 'Phinx\\Console\\Command\\Breakpoint' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php', + 'Phinx\\Console\\Command\\Create' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Create.php', + 'Phinx\\Console\\Command\\Init' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Init.php', + 'Phinx\\Console\\Command\\Migrate' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php', + 'Phinx\\Console\\Command\\Rollback' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php', + 'Phinx\\Console\\Command\\SeedCreate' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php', + 'Phinx\\Console\\Command\\SeedRun' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php', + 'Phinx\\Console\\Command\\Status' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Status.php', + 'Phinx\\Console\\Command\\Test' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Test.php', + 'Phinx\\Console\\PhinxApplication' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php', + 'Phinx\\Db\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.php', + 'Phinx\\Db\\Adapter\\AdapterFactory' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php', + 'Phinx\\Db\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php', + 'Phinx\\Db\\Adapter\\AdapterWrapper' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php', + 'Phinx\\Db\\Adapter\\MysqlAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php', + 'Phinx\\Db\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php', + 'Phinx\\Db\\Adapter\\PostgresAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php', + 'Phinx\\Db\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php', + 'Phinx\\Db\\Adapter\\SQLiteAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php', + 'Phinx\\Db\\Adapter\\SqlServerAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php', + 'Phinx\\Db\\Adapter\\TablePrefixAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php', + 'Phinx\\Db\\Adapter\\TimedOutputAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php', + 'Phinx\\Db\\Adapter\\WrapperInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php', + 'Phinx\\Db\\Table' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table.php', + 'Phinx\\Db\\Table\\Column' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Column.php', + 'Phinx\\Db\\Table\\ForeignKey' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php', + 'Phinx\\Db\\Table\\Index' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Index.php', + 'Phinx\\Migration\\AbstractMigration' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php', + 'Phinx\\Migration\\AbstractTemplateCreation' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php', + 'Phinx\\Migration\\CreationInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php', + 'Phinx\\Migration\\IrreversibleMigrationException' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php', + 'Phinx\\Migration\\Manager' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/Manager.php', + 'Phinx\\Migration\\Manager\\Environment' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php', + 'Phinx\\Migration\\MigrationInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.php', + 'Phinx\\Seed\\AbstractSeed' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php', + 'Phinx\\Seed\\SeedInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Seed/SeedInterface.php', + 'Phinx\\Util\\Util' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Util/Util.php', + 'Phinx\\Wrapper\\TextWrapper' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php', + 'Pimple\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Container.php', + 'Pimple\\Exception\\ExpectedInvokableException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php', + 'Pimple\\Exception\\FrozenServiceException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php', + 'Pimple\\Exception\\InvalidServiceIdentifierException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php', + 'Pimple\\Exception\\UnknownIdentifierException' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php', + 'Pimple\\Psr11\\Container' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Psr11/Container.php', + 'Pimple\\Psr11\\ServiceLocator' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php', + 'Pimple\\ServiceIterator' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/ServiceIterator.php', + 'Pimple\\ServiceProviderInterface' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/ServiceProviderInterface.php', + 'Pimple\\Tests\\Fixtures\\Invokable' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php', + 'Pimple\\Tests\\Fixtures\\NonInvokable' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php', + 'Pimple\\Tests\\Fixtures\\PimpleServiceProvider' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php', + 'Pimple\\Tests\\Fixtures\\Service' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php', + 'Pimple\\Tests\\PimpleServiceProviderInterfaceTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php', + 'Pimple\\Tests\\PimpleTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/PimpleTest.php', + 'Pimple\\Tests\\Psr11\\ContainerTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php', + 'Pimple\\Tests\\Psr11\\ServiceLocatorTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php', + 'Pimple\\Tests\\ServiceIteratorTest' => __DIR__ . '/..' . '/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php', + 'Prophecy\\Argument' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument.php', + 'Prophecy\\Argument\\ArgumentsWildcard' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php', + 'Prophecy\\Argument\\Token\\AnyValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php', + 'Prophecy\\Argument\\Token\\AnyValuesToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php', + 'Prophecy\\Argument\\Token\\ApproximateValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php', + 'Prophecy\\Argument\\Token\\ArrayCountToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php', + 'Prophecy\\Argument\\Token\\ArrayEntryToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php', + 'Prophecy\\Argument\\Token\\ArrayEveryEntryToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php', + 'Prophecy\\Argument\\Token\\CallbackToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php', + 'Prophecy\\Argument\\Token\\ExactValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php', + 'Prophecy\\Argument\\Token\\IdenticalValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php', + 'Prophecy\\Argument\\Token\\LogicalAndToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php', + 'Prophecy\\Argument\\Token\\LogicalNotToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php', + 'Prophecy\\Argument\\Token\\ObjectStateToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php', + 'Prophecy\\Argument\\Token\\StringContainsToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php', + 'Prophecy\\Argument\\Token\\TokenInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php', + 'Prophecy\\Argument\\Token\\TypeToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php', + 'Prophecy\\Call\\Call' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Call/Call.php', + 'Prophecy\\Call\\CallCenter' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Call/CallCenter.php', + 'Prophecy\\Comparator\\ClosureComparator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php', + 'Prophecy\\Comparator\\Factory' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Comparator/Factory.php', + 'Prophecy\\Comparator\\ProphecyComparator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php', + 'Prophecy\\Doubler\\CachedDoubler' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php', + 'Prophecy\\Doubler\\ClassPatch\\ClassPatchInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php', + 'Prophecy\\Doubler\\ClassPatch\\DisableConstructorPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\HhvmExceptionPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\KeywordPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\MagicCallPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\ProphecySubjectPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\ReflectionClassNewInstancePatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php', + 'Prophecy\\Doubler\\ClassPatch\\SplFileInfoPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php', + 'Prophecy\\Doubler\\ClassPatch\\TraversablePatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php', + 'Prophecy\\Doubler\\DoubleInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php', + 'Prophecy\\Doubler\\Doubler' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php', + 'Prophecy\\Doubler\\Generator\\ClassCodeGenerator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php', + 'Prophecy\\Doubler\\Generator\\ClassCreator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php', + 'Prophecy\\Doubler\\Generator\\ClassMirror' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php', + 'Prophecy\\Doubler\\Generator\\Node\\ArgumentNode' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php', + 'Prophecy\\Doubler\\Generator\\Node\\ClassNode' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php', + 'Prophecy\\Doubler\\Generator\\Node\\MethodNode' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php', + 'Prophecy\\Doubler\\Generator\\ReflectionInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php', + 'Prophecy\\Doubler\\Generator\\TypeHintReference' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php', + 'Prophecy\\Doubler\\LazyDouble' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php', + 'Prophecy\\Doubler\\NameGenerator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php', + 'Prophecy\\Exception\\Call\\UnexpectedCallException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php', + 'Prophecy\\Exception\\Doubler\\ClassCreatorException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php', + 'Prophecy\\Exception\\Doubler\\ClassMirrorException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php', + 'Prophecy\\Exception\\Doubler\\ClassNotFoundException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php', + 'Prophecy\\Exception\\Doubler\\DoubleException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php', + 'Prophecy\\Exception\\Doubler\\DoublerException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php', + 'Prophecy\\Exception\\Doubler\\InterfaceNotFoundException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php', + 'Prophecy\\Exception\\Doubler\\MethodNotExtendableException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php', + 'Prophecy\\Exception\\Doubler\\MethodNotFoundException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php', + 'Prophecy\\Exception\\Doubler\\ReturnByReferenceException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php', + 'Prophecy\\Exception\\Exception' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Exception.php', + 'Prophecy\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php', + 'Prophecy\\Exception\\Prediction\\AggregateException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php', + 'Prophecy\\Exception\\Prediction\\FailedPredictionException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php', + 'Prophecy\\Exception\\Prediction\\NoCallsException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php', + 'Prophecy\\Exception\\Prediction\\PredictionException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php', + 'Prophecy\\Exception\\Prediction\\UnexpectedCallsCountException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php', + 'Prophecy\\Exception\\Prediction\\UnexpectedCallsException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php', + 'Prophecy\\Exception\\Prophecy\\MethodProphecyException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php', + 'Prophecy\\Exception\\Prophecy\\ObjectProphecyException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php', + 'Prophecy\\Exception\\Prophecy\\ProphecyException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php', + 'Prophecy\\PhpDocumentor\\ClassAndInterfaceTagRetriever' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php', + 'Prophecy\\PhpDocumentor\\ClassTagRetriever' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php', + 'Prophecy\\PhpDocumentor\\LegacyClassTagRetriever' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php', + 'Prophecy\\PhpDocumentor\\MethodTagRetrieverInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php', + 'Prophecy\\Prediction\\CallPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php', + 'Prophecy\\Prediction\\CallTimesPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php', + 'Prophecy\\Prediction\\CallbackPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php', + 'Prophecy\\Prediction\\NoCallsPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php', + 'Prophecy\\Prediction\\PredictionInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php', + 'Prophecy\\Promise\\CallbackPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php', + 'Prophecy\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php', + 'Prophecy\\Promise\\ReturnArgumentPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php', + 'Prophecy\\Promise\\ReturnPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php', + 'Prophecy\\Promise\\ThrowPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php', + 'Prophecy\\Prophecy\\MethodProphecy' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php', + 'Prophecy\\Prophecy\\ObjectProphecy' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php', + 'Prophecy\\Prophecy\\ProphecyInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php', + 'Prophecy\\Prophecy\\ProphecySubjectInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php', + 'Prophecy\\Prophecy\\Revealer' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php', + 'Prophecy\\Prophecy\\RevealerInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php', + 'Prophecy\\Prophet' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophet.php', + 'Prophecy\\Util\\ExportUtil' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php', + 'Prophecy\\Util\\StringUtil' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Util/StringUtil.php', + 'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\SimpleCache\\CacheException' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/simple-cache/src/InvalidArgumentException.php', + 'RKA\\Middleware\\IpAddress' => __DIR__ . '/..' . '/akrabat/rka-ip-address-middleware/src/IpAddress.php', + 'Ramsey\\Uuid\\BinaryUtils' => __DIR__ . '/..' . '/ramsey/uuid/src/BinaryUtils.php', + 'Ramsey\\Uuid\\Builder\\DefaultUuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/DefaultUuidBuilder.php', + 'Ramsey\\Uuid\\Builder\\DegradedUuidBuilder' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/DegradedUuidBuilder.php', + 'Ramsey\\Uuid\\Builder\\UuidBuilderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Builder/UuidBuilderInterface.php', + 'Ramsey\\Uuid\\Codec\\CodecInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/CodecInterface.php', + 'Ramsey\\Uuid\\Codec\\GuidStringCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/GuidStringCodec.php', + 'Ramsey\\Uuid\\Codec\\OrderedTimeCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/OrderedTimeCodec.php', + 'Ramsey\\Uuid\\Codec\\StringCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/StringCodec.php', + 'Ramsey\\Uuid\\Codec\\TimestampFirstCombCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/TimestampFirstCombCodec.php', + 'Ramsey\\Uuid\\Codec\\TimestampLastCombCodec' => __DIR__ . '/..' . '/ramsey/uuid/src/Codec/TimestampLastCombCodec.php', + 'Ramsey\\Uuid\\Converter\\NumberConverterInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/NumberConverterInterface.php', + 'Ramsey\\Uuid\\Converter\\Number\\BigNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/BigNumberConverter.php', + 'Ramsey\\Uuid\\Converter\\Number\\DegradedNumberConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Number/DegradedNumberConverter.php', + 'Ramsey\\Uuid\\Converter\\TimeConverterInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/TimeConverterInterface.php', + 'Ramsey\\Uuid\\Converter\\Time\\BigNumberTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/BigNumberTimeConverter.php', + 'Ramsey\\Uuid\\Converter\\Time\\DegradedTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/DegradedTimeConverter.php', + 'Ramsey\\Uuid\\Converter\\Time\\PhpTimeConverter' => __DIR__ . '/..' . '/ramsey/uuid/src/Converter/Time/PhpTimeConverter.php', + 'Ramsey\\Uuid\\DegradedUuid' => __DIR__ . '/..' . '/ramsey/uuid/src/DegradedUuid.php', + 'Ramsey\\Uuid\\Exception\\InvalidUuidStringException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/InvalidUuidStringException.php', + 'Ramsey\\Uuid\\Exception\\UnsatisfiedDependencyException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/UnsatisfiedDependencyException.php', + 'Ramsey\\Uuid\\Exception\\UnsupportedOperationException' => __DIR__ . '/..' . '/ramsey/uuid/src/Exception/UnsupportedOperationException.php', + 'Ramsey\\Uuid\\FeatureSet' => __DIR__ . '/..' . '/ramsey/uuid/src/FeatureSet.php', + 'Ramsey\\Uuid\\Generator\\CombGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/CombGenerator.php', + 'Ramsey\\Uuid\\Generator\\DefaultTimeGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/DefaultTimeGenerator.php', + 'Ramsey\\Uuid\\Generator\\MtRandGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/MtRandGenerator.php', + 'Ramsey\\Uuid\\Generator\\OpenSslGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/OpenSslGenerator.php', + 'Ramsey\\Uuid\\Generator\\PeclUuidRandomGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidRandomGenerator.php', + 'Ramsey\\Uuid\\Generator\\PeclUuidTimeGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/PeclUuidTimeGenerator.php', + 'Ramsey\\Uuid\\Generator\\RandomBytesGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomBytesGenerator.php', + 'Ramsey\\Uuid\\Generator\\RandomGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomGeneratorFactory.php', + 'Ramsey\\Uuid\\Generator\\RandomGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomGeneratorInterface.php', + 'Ramsey\\Uuid\\Generator\\RandomLibAdapter' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/RandomLibAdapter.php', + 'Ramsey\\Uuid\\Generator\\SodiumRandomGenerator' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/SodiumRandomGenerator.php', + 'Ramsey\\Uuid\\Generator\\TimeGeneratorFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/TimeGeneratorFactory.php', + 'Ramsey\\Uuid\\Generator\\TimeGeneratorInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Generator/TimeGeneratorInterface.php', + 'Ramsey\\Uuid\\Provider\\NodeProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/NodeProviderInterface.php', + 'Ramsey\\Uuid\\Provider\\Node\\FallbackNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/FallbackNodeProvider.php', + 'Ramsey\\Uuid\\Provider\\Node\\RandomNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/RandomNodeProvider.php', + 'Ramsey\\Uuid\\Provider\\Node\\SystemNodeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Node/SystemNodeProvider.php', + 'Ramsey\\Uuid\\Provider\\TimeProviderInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/TimeProviderInterface.php', + 'Ramsey\\Uuid\\Provider\\Time\\FixedTimeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Time/FixedTimeProvider.php', + 'Ramsey\\Uuid\\Provider\\Time\\SystemTimeProvider' => __DIR__ . '/..' . '/ramsey/uuid/src/Provider/Time/SystemTimeProvider.php', + 'Ramsey\\Uuid\\Uuid' => __DIR__ . '/..' . '/ramsey/uuid/src/Uuid.php', + 'Ramsey\\Uuid\\UuidFactory' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidFactory.php', + 'Ramsey\\Uuid\\UuidFactoryInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidFactoryInterface.php', + 'Ramsey\\Uuid\\UuidInterface' => __DIR__ . '/..' . '/ramsey/uuid/src/UuidInterface.php', + 'RateLimit\\AbstractRateLimiter' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/AbstractRateLimiter.php', + 'RateLimit\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Exception/ExceptionInterface.php', + 'RateLimit\\Exception\\RateLimitExceededException' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Exception/RateLimitExceededException.php', + 'RateLimit\\InMemoryRateLimiter' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/InMemoryRateLimiter.php', + 'RateLimit\\Middleware\\Identity\\AbstractIdentityResolver' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Middleware/Identity/AbstractIdentityResolver.php', + 'RateLimit\\Middleware\\Identity\\IdentityResolverInterface' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Middleware/Identity/IdentityResolverInterface.php', + 'RateLimit\\Middleware\\Identity\\IpAddressIdentityResolver' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Middleware/Identity/IpAddressIdentityResolver.php', + 'RateLimit\\Middleware\\Options' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Middleware/Options.php', + 'RateLimit\\Middleware\\RateLimitMiddleware' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/Middleware/RateLimitMiddleware.php', + 'RateLimit\\RateLimiterFactory' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/RateLimiterFactory.php', + 'RateLimit\\RateLimiterInterface' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/RateLimiterInterface.php', + 'RateLimit\\RedisRateLimiter' => __DIR__ . '/..' . '/wellingguzman/rate-limit/src/RedisRateLimiter.php', + 'SebastianBergmann\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php', + 'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Driver.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\HHVM' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/HHVM.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php', + 'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Xdebug.php', + 'SebastianBergmann\\CodeCoverage\\Exception' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Exception.php', + 'SebastianBergmann\\CodeCoverage\\Filter' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Filter.php', + 'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php', + 'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php', + 'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/AbstractNode.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Builder' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Builder.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Node\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/File.php', + 'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Iterator.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Clover' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Clover.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Crap4j.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php', + 'SebastianBergmann\\CodeCoverage\\Report\\PHP' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/PHP.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Text' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Text.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/File.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Method.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Node.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Project.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Report.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php', + 'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php', + 'SebastianBergmann\\CodeCoverage\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php', + 'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php', + 'SebastianBergmann\\CodeCoverage\\Util' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Util.php', + 'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => __DIR__ . '/..' . '/sebastian/code-unit-reverse-lookup/src/Wizard.php', + 'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php', + 'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php', + 'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php', + 'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php', + 'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php', + 'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php', + 'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php', + 'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php', + 'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php', + 'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php', + 'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php', + 'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php', + 'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php', + 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php', + 'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php', + 'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php', + 'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php', + 'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php', + 'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php', + 'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', + 'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', + 'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php', + 'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php', + 'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php', + 'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php', + 'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php', + 'SebastianBergmann\\GlobalState\\Blacklist' => __DIR__ . '/..' . '/sebastian/global-state/src/Blacklist.php', + 'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php', + 'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/Exception.php', + 'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php', + 'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/RuntimeException.php', + 'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php', + 'SebastianBergmann\\ObjectEnumerator\\Enumerator' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Enumerator.php', + 'SebastianBergmann\\ObjectEnumerator\\Exception' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Exception.php', + 'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/InvalidArgumentException.php', + 'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php', + 'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php', + 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php', + 'SebastianBergmann\\ResourceOperations\\ResourceOperations' => __DIR__ . '/..' . '/sebastian/resource-operations/src/ResourceOperations.php', + 'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php', + 'Slim\\App' => __DIR__ . '/..' . '/slim/slim/Slim/App.php', + 'Slim\\CallableResolver' => __DIR__ . '/..' . '/slim/slim/Slim/CallableResolver.php', + 'Slim\\CallableResolverAwareTrait' => __DIR__ . '/..' . '/slim/slim/Slim/CallableResolverAwareTrait.php', + 'Slim\\Collection' => __DIR__ . '/..' . '/slim/slim/Slim/Collection.php', + 'Slim\\Container' => __DIR__ . '/..' . '/slim/slim/Slim/Container.php', + 'Slim\\DefaultServicesProvider' => __DIR__ . '/..' . '/slim/slim/Slim/DefaultServicesProvider.php', + 'Slim\\DeferredCallable' => __DIR__ . '/..' . '/slim/slim/Slim/DeferredCallable.php', + 'Slim\\Exception\\ContainerException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/ContainerException.php', + 'Slim\\Exception\\ContainerValueNotFoundException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/ContainerValueNotFoundException.php', + 'Slim\\Exception\\InvalidMethodException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/InvalidMethodException.php', + 'Slim\\Exception\\MethodNotAllowedException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/MethodNotAllowedException.php', + 'Slim\\Exception\\NotFoundException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/NotFoundException.php', + 'Slim\\Exception\\SlimException' => __DIR__ . '/..' . '/slim/slim/Slim/Exception/SlimException.php', + 'Slim\\Handlers\\AbstractError' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/AbstractError.php', + 'Slim\\Handlers\\AbstractHandler' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/AbstractHandler.php', + 'Slim\\Handlers\\Error' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/Error.php', + 'Slim\\Handlers\\NotAllowed' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/NotAllowed.php', + 'Slim\\Handlers\\NotFound' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/NotFound.php', + 'Slim\\Handlers\\PhpError' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/PhpError.php', + 'Slim\\Handlers\\Strategies\\RequestResponse' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/Strategies/RequestResponse.php', + 'Slim\\Handlers\\Strategies\\RequestResponseArgs' => __DIR__ . '/..' . '/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php', + 'Slim\\Http\\Body' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Body.php', + 'Slim\\Http\\Cookies' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Cookies.php', + 'Slim\\Http\\Environment' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Environment.php', + 'Slim\\Http\\Headers' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Headers.php', + 'Slim\\Http\\Message' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Message.php', + 'Slim\\Http\\Request' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Request.php', + 'Slim\\Http\\RequestBody' => __DIR__ . '/..' . '/slim/slim/Slim/Http/RequestBody.php', + 'Slim\\Http\\Response' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Response.php', + 'Slim\\Http\\Stream' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Stream.php', + 'Slim\\Http\\UploadedFile' => __DIR__ . '/..' . '/slim/slim/Slim/Http/UploadedFile.php', + 'Slim\\Http\\Uri' => __DIR__ . '/..' . '/slim/slim/Slim/Http/Uri.php', + 'Slim\\Interfaces\\CallableResolverInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/CallableResolverInterface.php', + 'Slim\\Interfaces\\CollectionInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/CollectionInterface.php', + 'Slim\\Interfaces\\Http\\CookiesInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/Http/CookiesInterface.php', + 'Slim\\Interfaces\\Http\\EnvironmentInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/Http/EnvironmentInterface.php', + 'Slim\\Interfaces\\Http\\HeadersInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/Http/HeadersInterface.php', + 'Slim\\Interfaces\\InvocationStrategyInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/InvocationStrategyInterface.php', + 'Slim\\Interfaces\\RouteGroupInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/RouteGroupInterface.php', + 'Slim\\Interfaces\\RouteInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/RouteInterface.php', + 'Slim\\Interfaces\\RouterInterface' => __DIR__ . '/..' . '/slim/slim/Slim/Interfaces/RouterInterface.php', + 'Slim\\MiddlewareAwareTrait' => __DIR__ . '/..' . '/slim/slim/Slim/MiddlewareAwareTrait.php', + 'Slim\\Routable' => __DIR__ . '/..' . '/slim/slim/Slim/Routable.php', + 'Slim\\Route' => __DIR__ . '/..' . '/slim/slim/Slim/Route.php', + 'Slim\\RouteGroup' => __DIR__ . '/..' . '/slim/slim/Slim/RouteGroup.php', + 'Slim\\Router' => __DIR__ . '/..' . '/slim/slim/Slim/Router.php', + 'Slim\\Views\\Twig' => __DIR__ . '/..' . '/slim/twig-view/src/Twig.php', + 'Slim\\Views\\TwigExtension' => __DIR__ . '/..' . '/slim/twig-view/src/TwigExtension.php', + 'Symfony\\Component\\Config\\ConfigCache' => __DIR__ . '/..' . '/symfony/config/ConfigCache.php', + 'Symfony\\Component\\Config\\ConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactoryInterface.php', + 'Symfony\\Component\\Config\\ConfigCacheInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheInterface.php', + 'Symfony\\Component\\Config\\Definition\\ArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/ArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\BaseNode' => __DIR__ . '/..' . '/symfony/config/Definition/BaseNode.php', + 'Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/MergeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeParentInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NormalizationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NumericNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/TreeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ValidationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurationInterface.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\EnumNode' => __DIR__ . '/..' . '/symfony/config/Definition/EnumNode.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/DuplicateKeyException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/Exception.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidTypeException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php', + 'Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php', + 'Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php', + 'Symfony\\Component\\Config\\Definition\\NodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/NodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\NumericNode' => __DIR__ . '/..' . '/symfony/config/Definition/NumericNode.php', + 'Symfony\\Component\\Config\\Definition\\Processor' => __DIR__ . '/..' . '/symfony/config/Definition/Processor.php', + 'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypeNodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypedArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\ScalarNode' => __DIR__ . '/..' . '/symfony/config/Definition/ScalarNode.php', + 'Symfony\\Component\\Config\\Definition\\VariableNode' => __DIR__ . '/..' . '/symfony/config/Definition/VariableNode.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderLoadException.php', + 'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', + 'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php', + 'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php', + 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php', + 'Symfony\\Component\\Config\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/FileLoader.php', + 'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/GlobFileLoader.php', + 'Symfony\\Component\\Config\\Loader\\Loader' => __DIR__ . '/..' . '/symfony/config/Loader/Loader.php', + 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderInterface.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolver' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolver.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolverInterface.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCache.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ResourceCheckerInterface' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerInterface.php', + 'Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/ClassExistenceResource.php', + 'Symfony\\Component\\Config\\Resource\\ComposerResource' => __DIR__ . '/..' . '/symfony/config/Resource/ComposerResource.php', + 'Symfony\\Component\\Config\\Resource\\DirectoryResource' => __DIR__ . '/..' . '/symfony/config/Resource/DirectoryResource.php', + 'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileExistenceResource.php', + 'Symfony\\Component\\Config\\Resource\\FileResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileResource.php', + 'Symfony\\Component\\Config\\Resource\\GlobResource' => __DIR__ . '/..' . '/symfony/config/Resource/GlobResource.php', + 'Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => __DIR__ . '/..' . '/symfony/config/Resource/ReflectionClassResource.php', + 'Symfony\\Component\\Config\\Resource\\ResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/ResourceInterface.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceChecker.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceInterface.php', + 'Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/InvalidXmlException.php', + 'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/XmlParsingException.php', + 'Symfony\\Component\\Config\\Util\\XmlUtils' => __DIR__ . '/..' . '/symfony/config/Util/XmlUtils.php', + 'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php', + 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php', + 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php', + 'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Translation\\Catalogue\\AbstractOperation' => __DIR__ . '/..' . '/symfony/translation/Catalogue/AbstractOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\MergeOperation' => __DIR__ . '/..' . '/symfony/translation/Catalogue/MergeOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\OperationInterface' => __DIR__ . '/..' . '/symfony/translation/Catalogue/OperationInterface.php', + 'Symfony\\Component\\Translation\\Catalogue\\TargetOperation' => __DIR__ . '/..' . '/symfony/translation/Catalogue/TargetOperation.php', + 'Symfony\\Component\\Translation\\Command\\XliffLintCommand' => __DIR__ . '/..' . '/symfony/translation/Command/XliffLintCommand.php', + 'Symfony\\Component\\Translation\\DataCollectorTranslator' => __DIR__ . '/..' . '/symfony/translation/DataCollectorTranslator.php', + 'Symfony\\Component\\Translation\\DataCollector\\TranslationDataCollector' => __DIR__ . '/..' . '/symfony/translation/DataCollector/TranslationDataCollector.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationDumperPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslationDumperPass.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslationExtractorPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslationExtractorPass.php', + 'Symfony\\Component\\Translation\\DependencyInjection\\TranslatorPass' => __DIR__ . '/..' . '/symfony/translation/DependencyInjection/TranslatorPass.php', + 'Symfony\\Component\\Translation\\Dumper\\CsvFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/CsvFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/translation/Dumper/DumperInterface.php', + 'Symfony\\Component\\Translation\\Dumper\\FileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/FileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\IcuResFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/IcuResFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\IniFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/IniFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\JsonFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/JsonFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\MoFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/MoFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\PhpFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/PhpFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\PoFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/PoFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\QtFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/QtFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\XliffFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/XliffFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\YamlFileDumper' => __DIR__ . '/..' . '/symfony/translation/Dumper/YamlFileDumper.php', + 'Symfony\\Component\\Translation\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/translation/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Translation\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/translation/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Translation\\Exception\\InvalidResourceException' => __DIR__ . '/..' . '/symfony/translation/Exception/InvalidResourceException.php', + 'Symfony\\Component\\Translation\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/translation/Exception/LogicException.php', + 'Symfony\\Component\\Translation\\Exception\\NotFoundResourceException' => __DIR__ . '/..' . '/symfony/translation/Exception/NotFoundResourceException.php', + 'Symfony\\Component\\Translation\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/translation/Exception/RuntimeException.php', + 'Symfony\\Component\\Translation\\Extractor\\AbstractFileExtractor' => __DIR__ . '/..' . '/symfony/translation/Extractor/AbstractFileExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\ChainExtractor' => __DIR__ . '/..' . '/symfony/translation/Extractor/ChainExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\ExtractorInterface' => __DIR__ . '/..' . '/symfony/translation/Extractor/ExtractorInterface.php', + 'Symfony\\Component\\Translation\\Extractor\\PhpExtractor' => __DIR__ . '/..' . '/symfony/translation/Extractor/PhpExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\PhpStringTokenParser' => __DIR__ . '/..' . '/symfony/translation/Extractor/PhpStringTokenParser.php', + 'Symfony\\Component\\Translation\\Formatter\\ChoiceMessageFormatterInterface' => __DIR__ . '/..' . '/symfony/translation/Formatter/ChoiceMessageFormatterInterface.php', + 'Symfony\\Component\\Translation\\Formatter\\MessageFormatter' => __DIR__ . '/..' . '/symfony/translation/Formatter/MessageFormatter.php', + 'Symfony\\Component\\Translation\\Formatter\\MessageFormatterInterface' => __DIR__ . '/..' . '/symfony/translation/Formatter/MessageFormatterInterface.php', + 'Symfony\\Component\\Translation\\IdentityTranslator' => __DIR__ . '/..' . '/symfony/translation/IdentityTranslator.php', + 'Symfony\\Component\\Translation\\Interval' => __DIR__ . '/..' . '/symfony/translation/Interval.php', + 'Symfony\\Component\\Translation\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/ArrayLoader.php', + 'Symfony\\Component\\Translation\\Loader\\CsvFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/CsvFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/FileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/IcuDatFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IcuResFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/IcuResFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IniFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/IniFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\JsonFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/JsonFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/translation/Loader/LoaderInterface.php', + 'Symfony\\Component\\Translation\\Loader\\MoFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/MoFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\PoFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/PoFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\QtFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/QtFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\XliffFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/XliffFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/translation/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Translation\\LoggingTranslator' => __DIR__ . '/..' . '/symfony/translation/LoggingTranslator.php', + 'Symfony\\Component\\Translation\\MessageCatalogue' => __DIR__ . '/..' . '/symfony/translation/MessageCatalogue.php', + 'Symfony\\Component\\Translation\\MessageCatalogueInterface' => __DIR__ . '/..' . '/symfony/translation/MessageCatalogueInterface.php', + 'Symfony\\Component\\Translation\\MessageSelector' => __DIR__ . '/..' . '/symfony/translation/MessageSelector.php', + 'Symfony\\Component\\Translation\\MetadataAwareInterface' => __DIR__ . '/..' . '/symfony/translation/MetadataAwareInterface.php', + 'Symfony\\Component\\Translation\\PluralizationRules' => __DIR__ . '/..' . '/symfony/translation/PluralizationRules.php', + 'Symfony\\Component\\Translation\\Reader\\TranslationReader' => __DIR__ . '/..' . '/symfony/translation/Reader/TranslationReader.php', + 'Symfony\\Component\\Translation\\Reader\\TranslationReaderInterface' => __DIR__ . '/..' . '/symfony/translation/Reader/TranslationReaderInterface.php', + 'Symfony\\Component\\Translation\\Translator' => __DIR__ . '/..' . '/symfony/translation/Translator.php', + 'Symfony\\Component\\Translation\\TranslatorBagInterface' => __DIR__ . '/..' . '/symfony/translation/TranslatorBagInterface.php', + 'Symfony\\Component\\Translation\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/translation/TranslatorInterface.php', + 'Symfony\\Component\\Translation\\Util\\ArrayConverter' => __DIR__ . '/..' . '/symfony/translation/Util/ArrayConverter.php', + 'Symfony\\Component\\Translation\\Writer\\TranslationWriter' => __DIR__ . '/..' . '/symfony/translation/Writer/TranslationWriter.php', + 'Symfony\\Component\\Translation\\Writer\\TranslationWriterInterface' => __DIR__ . '/..' . '/symfony/translation/Writer/TranslationWriterInterface.php', + 'Symfony\\Component\\Validator\\Constraint' => __DIR__ . '/..' . '/symfony/validator/Constraint.php', + 'Symfony\\Component\\Validator\\ConstraintValidator' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidator.php', + 'Symfony\\Component\\Validator\\ConstraintValidatorFactory' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidatorFactory.php', + 'Symfony\\Component\\Validator\\ConstraintValidatorFactoryInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidatorFactoryInterface.php', + 'Symfony\\Component\\Validator\\ConstraintValidatorInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintValidatorInterface.php', + 'Symfony\\Component\\Validator\\ConstraintViolation' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolation.php', + 'Symfony\\Component\\Validator\\ConstraintViolationInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolationInterface.php', + 'Symfony\\Component\\Validator\\ConstraintViolationList' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolationList.php', + 'Symfony\\Component\\Validator\\ConstraintViolationListInterface' => __DIR__ . '/..' . '/symfony/validator/ConstraintViolationListInterface.php', + 'Symfony\\Component\\Validator\\Constraints\\AbstractComparison' => __DIR__ . '/..' . '/symfony/validator/Constraints/AbstractComparison.php', + 'Symfony\\Component\\Validator\\Constraints\\AbstractComparisonValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/AbstractComparisonValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\All' => __DIR__ . '/..' . '/symfony/validator/Constraints/All.php', + 'Symfony\\Component\\Validator\\Constraints\\AllValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/AllValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Bic' => __DIR__ . '/..' . '/symfony/validator/Constraints/Bic.php', + 'Symfony\\Component\\Validator\\Constraints\\BicValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/BicValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Blank' => __DIR__ . '/..' . '/symfony/validator/Constraints/Blank.php', + 'Symfony\\Component\\Validator\\Constraints\\BlankValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/BlankValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Callback' => __DIR__ . '/..' . '/symfony/validator/Constraints/Callback.php', + 'Symfony\\Component\\Validator\\Constraints\\CallbackValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CallbackValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\CardScheme' => __DIR__ . '/..' . '/symfony/validator/Constraints/CardScheme.php', + 'Symfony\\Component\\Validator\\Constraints\\CardSchemeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CardSchemeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Choice' => __DIR__ . '/..' . '/symfony/validator/Constraints/Choice.php', + 'Symfony\\Component\\Validator\\Constraints\\ChoiceValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ChoiceValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Collection' => __DIR__ . '/..' . '/symfony/validator/Constraints/Collection.php', + 'Symfony\\Component\\Validator\\Constraints\\CollectionValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CollectionValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Composite' => __DIR__ . '/..' . '/symfony/validator/Constraints/Composite.php', + 'Symfony\\Component\\Validator\\Constraints\\Count' => __DIR__ . '/..' . '/symfony/validator/Constraints/Count.php', + 'Symfony\\Component\\Validator\\Constraints\\CountValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CountValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Country' => __DIR__ . '/..' . '/symfony/validator/Constraints/Country.php', + 'Symfony\\Component\\Validator\\Constraints\\CountryValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CountryValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Currency' => __DIR__ . '/..' . '/symfony/validator/Constraints/Currency.php', + 'Symfony\\Component\\Validator\\Constraints\\CurrencyValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/CurrencyValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Date' => __DIR__ . '/..' . '/symfony/validator/Constraints/Date.php', + 'Symfony\\Component\\Validator\\Constraints\\DateTime' => __DIR__ . '/..' . '/symfony/validator/Constraints/DateTime.php', + 'Symfony\\Component\\Validator\\Constraints\\DateTimeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/DateTimeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\DateValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/DateValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Email' => __DIR__ . '/..' . '/symfony/validator/Constraints/Email.php', + 'Symfony\\Component\\Validator\\Constraints\\EmailValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/EmailValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\EqualTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/EqualTo.php', + 'Symfony\\Component\\Validator\\Constraints\\EqualToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/EqualToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Existence' => __DIR__ . '/..' . '/symfony/validator/Constraints/Existence.php', + 'Symfony\\Component\\Validator\\Constraints\\Expression' => __DIR__ . '/..' . '/symfony/validator/Constraints/Expression.php', + 'Symfony\\Component\\Validator\\Constraints\\ExpressionValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ExpressionValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\File' => __DIR__ . '/..' . '/symfony/validator/Constraints/File.php', + 'Symfony\\Component\\Validator\\Constraints\\FileValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/FileValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThan' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThan.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqual' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThanOrEqual.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThanOrEqualValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThanOrEqualValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\GreaterThanValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/GreaterThanValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\GroupSequence' => __DIR__ . '/..' . '/symfony/validator/Constraints/GroupSequence.php', + 'Symfony\\Component\\Validator\\Constraints\\GroupSequenceProvider' => __DIR__ . '/..' . '/symfony/validator/Constraints/GroupSequenceProvider.php', + 'Symfony\\Component\\Validator\\Constraints\\Iban' => __DIR__ . '/..' . '/symfony/validator/Constraints/Iban.php', + 'Symfony\\Component\\Validator\\Constraints\\IbanValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IbanValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IdenticalTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/IdenticalTo.php', + 'Symfony\\Component\\Validator\\Constraints\\IdenticalToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IdenticalToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Image' => __DIR__ . '/..' . '/symfony/validator/Constraints/Image.php', + 'Symfony\\Component\\Validator\\Constraints\\ImageValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ImageValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Ip' => __DIR__ . '/..' . '/symfony/validator/Constraints/Ip.php', + 'Symfony\\Component\\Validator\\Constraints\\IpValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IpValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IsFalse' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsFalse.php', + 'Symfony\\Component\\Validator\\Constraints\\IsFalseValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsFalseValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IsNull' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsNull.php', + 'Symfony\\Component\\Validator\\Constraints\\IsNullValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsNullValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\IsTrue' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsTrue.php', + 'Symfony\\Component\\Validator\\Constraints\\IsTrueValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsTrueValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Isbn' => __DIR__ . '/..' . '/symfony/validator/Constraints/Isbn.php', + 'Symfony\\Component\\Validator\\Constraints\\IsbnValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IsbnValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Issn' => __DIR__ . '/..' . '/symfony/validator/Constraints/Issn.php', + 'Symfony\\Component\\Validator\\Constraints\\IssnValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/IssnValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Language' => __DIR__ . '/..' . '/symfony/validator/Constraints/Language.php', + 'Symfony\\Component\\Validator\\Constraints\\LanguageValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LanguageValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Length' => __DIR__ . '/..' . '/symfony/validator/Constraints/Length.php', + 'Symfony\\Component\\Validator\\Constraints\\LengthValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LengthValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThan' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThan.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqual' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThanOrEqual.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThanOrEqualValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThanOrEqualValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\LessThanValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LessThanValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Locale' => __DIR__ . '/..' . '/symfony/validator/Constraints/Locale.php', + 'Symfony\\Component\\Validator\\Constraints\\LocaleValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LocaleValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Luhn' => __DIR__ . '/..' . '/symfony/validator/Constraints/Luhn.php', + 'Symfony\\Component\\Validator\\Constraints\\LuhnValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/LuhnValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotBlank' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotBlank.php', + 'Symfony\\Component\\Validator\\Constraints\\NotBlankValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotBlankValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotEqualTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotEqualTo.php', + 'Symfony\\Component\\Validator\\Constraints\\NotEqualToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotEqualToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalTo' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotIdenticalTo.php', + 'Symfony\\Component\\Validator\\Constraints\\NotIdenticalToValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotIdenticalToValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\NotNull' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotNull.php', + 'Symfony\\Component\\Validator\\Constraints\\NotNullValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/NotNullValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Optional' => __DIR__ . '/..' . '/symfony/validator/Constraints/Optional.php', + 'Symfony\\Component\\Validator\\Constraints\\Range' => __DIR__ . '/..' . '/symfony/validator/Constraints/Range.php', + 'Symfony\\Component\\Validator\\Constraints\\RangeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/RangeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Regex' => __DIR__ . '/..' . '/symfony/validator/Constraints/Regex.php', + 'Symfony\\Component\\Validator\\Constraints\\RegexValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/RegexValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Required' => __DIR__ . '/..' . '/symfony/validator/Constraints/Required.php', + 'Symfony\\Component\\Validator\\Constraints\\Time' => __DIR__ . '/..' . '/symfony/validator/Constraints/Time.php', + 'Symfony\\Component\\Validator\\Constraints\\TimeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/TimeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Traverse' => __DIR__ . '/..' . '/symfony/validator/Constraints/Traverse.php', + 'Symfony\\Component\\Validator\\Constraints\\Type' => __DIR__ . '/..' . '/symfony/validator/Constraints/Type.php', + 'Symfony\\Component\\Validator\\Constraints\\TypeValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/TypeValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Url' => __DIR__ . '/..' . '/symfony/validator/Constraints/Url.php', + 'Symfony\\Component\\Validator\\Constraints\\UrlValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/UrlValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Uuid' => __DIR__ . '/..' . '/symfony/validator/Constraints/Uuid.php', + 'Symfony\\Component\\Validator\\Constraints\\UuidValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/UuidValidator.php', + 'Symfony\\Component\\Validator\\Constraints\\Valid' => __DIR__ . '/..' . '/symfony/validator/Constraints/Valid.php', + 'Symfony\\Component\\Validator\\Constraints\\ValidValidator' => __DIR__ . '/..' . '/symfony/validator/Constraints/ValidValidator.php', + 'Symfony\\Component\\Validator\\ContainerConstraintValidatorFactory' => __DIR__ . '/..' . '/symfony/validator/ContainerConstraintValidatorFactory.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContext' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContext.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactory' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContextFactory.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContextFactoryInterface' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContextFactoryInterface.php', + 'Symfony\\Component\\Validator\\Context\\ExecutionContextInterface' => __DIR__ . '/..' . '/symfony/validator/Context/ExecutionContextInterface.php', + 'Symfony\\Component\\Validator\\DataCollector\\ValidatorDataCollector' => __DIR__ . '/..' . '/symfony/validator/DataCollector/ValidatorDataCollector.php', + 'Symfony\\Component\\Validator\\DependencyInjection\\AddConstraintValidatorsPass' => __DIR__ . '/..' . '/symfony/validator/DependencyInjection/AddConstraintValidatorsPass.php', + 'Symfony\\Component\\Validator\\DependencyInjection\\AddValidatorInitializersPass' => __DIR__ . '/..' . '/symfony/validator/DependencyInjection/AddValidatorInitializersPass.php', + 'Symfony\\Component\\Validator\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/symfony/validator/Exception/BadMethodCallException.php', + 'Symfony\\Component\\Validator\\Exception\\ConstraintDefinitionException' => __DIR__ . '/..' . '/symfony/validator/Exception/ConstraintDefinitionException.php', + 'Symfony\\Component\\Validator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/validator/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Validator\\Exception\\GroupDefinitionException' => __DIR__ . '/..' . '/symfony/validator/Exception/GroupDefinitionException.php', + 'Symfony\\Component\\Validator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/validator/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Validator\\Exception\\InvalidOptionsException' => __DIR__ . '/..' . '/symfony/validator/Exception/InvalidOptionsException.php', + 'Symfony\\Component\\Validator\\Exception\\MappingException' => __DIR__ . '/..' . '/symfony/validator/Exception/MappingException.php', + 'Symfony\\Component\\Validator\\Exception\\MissingOptionsException' => __DIR__ . '/..' . '/symfony/validator/Exception/MissingOptionsException.php', + 'Symfony\\Component\\Validator\\Exception\\NoSuchMetadataException' => __DIR__ . '/..' . '/symfony/validator/Exception/NoSuchMetadataException.php', + 'Symfony\\Component\\Validator\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/validator/Exception/OutOfBoundsException.php', + 'Symfony\\Component\\Validator\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/validator/Exception/RuntimeException.php', + 'Symfony\\Component\\Validator\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/validator/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\Validator\\Exception\\UnsupportedMetadataException' => __DIR__ . '/..' . '/symfony/validator/Exception/UnsupportedMetadataException.php', + 'Symfony\\Component\\Validator\\Exception\\ValidatorException' => __DIR__ . '/..' . '/symfony/validator/Exception/ValidatorException.php', + 'Symfony\\Component\\Validator\\GroupSequenceProviderInterface' => __DIR__ . '/..' . '/symfony/validator/GroupSequenceProviderInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/Cache/CacheInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Cache\\DoctrineCache' => __DIR__ . '/..' . '/symfony/validator/Mapping/Cache/DoctrineCache.php', + 'Symfony\\Component\\Validator\\Mapping\\Cache\\Psr6Cache' => __DIR__ . '/..' . '/symfony/validator/Mapping/Cache/Psr6Cache.php', + 'Symfony\\Component\\Validator\\Mapping\\CascadingStrategy' => __DIR__ . '/..' . '/symfony/validator/Mapping/CascadingStrategy.php', + 'Symfony\\Component\\Validator\\Mapping\\ClassMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/ClassMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/ClassMetadataInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Factory\\BlackHoleMetadataFactory' => __DIR__ . '/..' . '/symfony/validator/Mapping/Factory/BlackHoleMetadataFactory.php', + 'Symfony\\Component\\Validator\\Mapping\\Factory\\LazyLoadingMetadataFactory' => __DIR__ . '/..' . '/symfony/validator/Mapping/Factory/LazyLoadingMetadataFactory.php', + 'Symfony\\Component\\Validator\\Mapping\\Factory\\MetadataFactoryInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/Factory/MetadataFactoryInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\GenericMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/GenericMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\GetterMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/GetterMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\AbstractLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/AbstractLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\AnnotationLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/AnnotationLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/FileLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\FilesLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/FilesLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderChain' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/LoaderChain.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/LoaderInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\StaticMethodLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/StaticMethodLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/XmlFileLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\XmlFilesLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/XmlFilesLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\Loader\\YamlFilesLoader' => __DIR__ . '/..' . '/symfony/validator/Mapping/Loader/YamlFilesLoader.php', + 'Symfony\\Component\\Validator\\Mapping\\MemberMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/MemberMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\MetadataInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/MetadataInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\PropertyMetadata' => __DIR__ . '/..' . '/symfony/validator/Mapping/PropertyMetadata.php', + 'Symfony\\Component\\Validator\\Mapping\\PropertyMetadataInterface' => __DIR__ . '/..' . '/symfony/validator/Mapping/PropertyMetadataInterface.php', + 'Symfony\\Component\\Validator\\Mapping\\TraversalStrategy' => __DIR__ . '/..' . '/symfony/validator/Mapping/TraversalStrategy.php', + 'Symfony\\Component\\Validator\\ObjectInitializerInterface' => __DIR__ . '/..' . '/symfony/validator/ObjectInitializerInterface.php', + 'Symfony\\Component\\Validator\\Test\\ConstraintValidatorTestCase' => __DIR__ . '/..' . '/symfony/validator/Test/ConstraintValidatorTestCase.php', + 'Symfony\\Component\\Validator\\Test\\ConstraintViolationAssertion' => __DIR__ . '/..' . '/symfony/validator/Test/ConstraintValidatorTestCase.php', + 'Symfony\\Component\\Validator\\Util\\PropertyPath' => __DIR__ . '/..' . '/symfony/validator/Util/PropertyPath.php', + 'Symfony\\Component\\Validator\\Validation' => __DIR__ . '/..' . '/symfony/validator/Validation.php', + 'Symfony\\Component\\Validator\\ValidatorBuilder' => __DIR__ . '/..' . '/symfony/validator/ValidatorBuilder.php', + 'Symfony\\Component\\Validator\\ValidatorBuilderInterface' => __DIR__ . '/..' . '/symfony/validator/ValidatorBuilderInterface.php', + 'Symfony\\Component\\Validator\\Validator\\ContextualValidatorInterface' => __DIR__ . '/..' . '/symfony/validator/Validator/ContextualValidatorInterface.php', + 'Symfony\\Component\\Validator\\Validator\\RecursiveContextualValidator' => __DIR__ . '/..' . '/symfony/validator/Validator/RecursiveContextualValidator.php', + 'Symfony\\Component\\Validator\\Validator\\RecursiveValidator' => __DIR__ . '/..' . '/symfony/validator/Validator/RecursiveValidator.php', + 'Symfony\\Component\\Validator\\Validator\\TraceableValidator' => __DIR__ . '/..' . '/symfony/validator/Validator/TraceableValidator.php', + 'Symfony\\Component\\Validator\\Validator\\ValidatorInterface' => __DIR__ . '/..' . '/symfony/validator/Validator/ValidatorInterface.php', + 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilder' => __DIR__ . '/..' . '/symfony/validator/Violation/ConstraintViolationBuilder.php', + 'Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilderInterface' => __DIR__ . '/..' . '/symfony/validator/Violation/ConstraintViolationBuilderInterface.php', + 'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', + 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', + 'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php', + 'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php', + 'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/yaml/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Yaml\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/yaml/Exception/ParseException.php', + 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php', + 'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php', + 'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php', + 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php', + 'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', + 'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Text_Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php', + 'Twig\\Cache\\CacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/CacheInterface.php', + 'Twig\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/FilesystemCache.php', + 'Twig\\Cache\\NullCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/NullCache.php', + 'Twig\\Compiler' => __DIR__ . '/..' . '/twig/twig/src/Compiler.php', + 'Twig\\Environment' => __DIR__ . '/..' . '/twig/twig/src/Environment.php', + 'Twig\\Error\\Error' => __DIR__ . '/..' . '/twig/twig/src/Error/Error.php', + 'Twig\\Error\\LoaderError' => __DIR__ . '/..' . '/twig/twig/src/Error/LoaderError.php', + 'Twig\\Error\\RuntimeError' => __DIR__ . '/..' . '/twig/twig/src/Error/RuntimeError.php', + 'Twig\\Error\\SyntaxError' => __DIR__ . '/..' . '/twig/twig/src/Error/SyntaxError.php', + 'Twig\\ExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser.php', + 'Twig\\ExtensionSet' => __DIR__ . '/..' . '/twig/twig/src/ExtensionSet.php', + 'Twig\\Extension\\AbstractExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/AbstractExtension.php', + 'Twig\\Extension\\CoreExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/CoreExtension.php', + 'Twig\\Extension\\DebugExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/DebugExtension.php', + 'Twig\\Extension\\EscaperExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/EscaperExtension.php', + 'Twig\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/ExtensionInterface.php', + 'Twig\\Extension\\GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/GlobalsInterface.php', + 'Twig\\Extension\\InitRuntimeInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/InitRuntimeInterface.php', + 'Twig\\Extension\\OptimizerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/OptimizerExtension.php', + 'Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/ProfilerExtension.php', + 'Twig\\Extension\\RuntimeExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', + 'Twig\\Extension\\SandboxExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/SandboxExtension.php', + 'Twig\\Extension\\StagingExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StagingExtension.php', + 'Twig\\Extension\\StringLoaderExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StringLoaderExtension.php', + 'Twig\\FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/src/FileExtensionEscapingStrategy.php', + 'Twig\\Lexer' => __DIR__ . '/..' . '/twig/twig/src/Lexer.php', + 'Twig\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ArrayLoader.php', + 'Twig\\Loader\\ChainLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ChainLoader.php', + 'Twig\\Loader\\ExistsLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/ExistsLoaderInterface.php', + 'Twig\\Loader\\FilesystemLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/FilesystemLoader.php', + 'Twig\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/LoaderInterface.php', + 'Twig\\Loader\\SourceContextLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/SourceContextLoaderInterface.php', + 'Twig\\Markup' => __DIR__ . '/..' . '/twig/twig/src/Markup.php', + 'Twig\\NodeTraverser' => __DIR__ . '/..' . '/twig/twig/src/NodeTraverser.php', + 'Twig\\NodeVisitor\\AbstractNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', + 'Twig\\NodeVisitor\\EscaperNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', + 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', + 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', + 'Twig\\NodeVisitor\\SandboxNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php', + 'Twig\\Node\\AutoEscapeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/AutoEscapeNode.php', + 'Twig\\Node\\BlockNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockNode.php', + 'Twig\\Node\\BlockReferenceNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockReferenceNode.php', + 'Twig\\Node\\BodyNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityNode.php', + 'Twig\\Node\\DoNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DoNode.php', + 'Twig\\Node\\EmbedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/EmbedNode.php', + 'Twig\\Node\\Expression\\AbstractExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AbstractExpression.php', + 'Twig\\Node\\Expression\\ArrayExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ArrayExpression.php', + 'Twig\\Node\\Expression\\AssignNameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AssignNameExpression.php', + 'Twig\\Node\\Expression\\Binary\\AbstractBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AbstractBinary.php', + 'Twig\\Node\\Expression\\Binary\\AddBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AddBinary.php', + 'Twig\\Node\\Expression\\Binary\\AndBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseAndBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseOrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseXorBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php', + 'Twig\\Node\\Expression\\Binary\\ConcatBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ConcatBinary.php', + 'Twig\\Node\\Expression\\Binary\\DivBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/DivBinary.php', + 'Twig\\Node\\Expression\\Binary\\EndsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\EqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/EqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\FloorDivBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/GreaterBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\InBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/InBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/LessBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\MatchesBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MatchesBinary.php', + 'Twig\\Node\\Expression\\Binary\\ModBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ModBinary.php', + 'Twig\\Node\\Expression\\Binary\\MulBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MulBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotInBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php', + 'Twig\\Node\\Expression\\Binary\\OrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', + 'Twig\\Node\\Expression\\Binary\\PowerBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', + 'Twig\\Node\\Expression\\Binary\\RangeBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\SubBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', + 'Twig\\Node\\Expression\\BlockReferenceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', + 'Twig\\Node\\Expression\\CallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/CallExpression.php', + 'Twig\\Node\\Expression\\ConditionalExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConditionalExpression.php', + 'Twig\\Node\\Expression\\ConstantExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConstantExpression.php', + 'Twig\\Node\\Expression\\FilterExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FilterExpression.php', + 'Twig\\Node\\Expression\\Filter\\DefaultFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php', + 'Twig\\Node\\Expression\\FunctionExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FunctionExpression.php', + 'Twig\\Node\\Expression\\GetAttrExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/GetAttrExpression.php', + 'Twig\\Node\\Expression\\MethodCallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/MethodCallExpression.php', + 'Twig\\Node\\Expression\\NameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NameExpression.php', + 'Twig\\Node\\Expression\\NullCoalesceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php', + 'Twig\\Node\\Expression\\ParentExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ParentExpression.php', + 'Twig\\Node\\Expression\\TempNameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/TempNameExpression.php', + 'Twig\\Node\\Expression\\TestExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/TestExpression.php', + 'Twig\\Node\\Expression\\Test\\ConstantTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/ConstantTest.php', + 'Twig\\Node\\Expression\\Test\\DefinedTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/DefinedTest.php', + 'Twig\\Node\\Expression\\Test\\DivisiblebyTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php', + 'Twig\\Node\\Expression\\Test\\EvenTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/EvenTest.php', + 'Twig\\Node\\Expression\\Test\\NullTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/NullTest.php', + 'Twig\\Node\\Expression\\Test\\OddTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/OddTest.php', + 'Twig\\Node\\Expression\\Test\\SameasTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/SameasTest.php', + 'Twig\\Node\\Expression\\Unary\\AbstractUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/AbstractUnary.php', + 'Twig\\Node\\Expression\\Unary\\NegUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', + 'Twig\\Node\\Expression\\Unary\\NotUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', + 'Twig\\Node\\Expression\\Unary\\PosUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\FlushNode' => __DIR__ . '/..' . '/twig/twig/src/Node/FlushNode.php', + 'Twig\\Node\\ForLoopNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForLoopNode.php', + 'Twig\\Node\\ForNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForNode.php', + 'Twig\\Node\\IfNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IfNode.php', + 'Twig\\Node\\ImportNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ImportNode.php', + 'Twig\\Node\\IncludeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IncludeNode.php', + 'Twig\\Node\\MacroNode' => __DIR__ . '/..' . '/twig/twig/src/Node/MacroNode.php', + 'Twig\\Node\\ModuleNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ModuleNode.php', + 'Twig\\Node\\Node' => __DIR__ . '/..' . '/twig/twig/src/Node/Node.php', + 'Twig\\Node\\NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeCaptureInterface.php', + 'Twig\\Node\\NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeOutputInterface.php', + 'Twig\\Node\\PrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/PrintNode.php', + 'Twig\\Node\\SandboxNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxNode.php', + 'Twig\\Node\\SandboxedPrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxedPrintNode.php', + 'Twig\\Node\\SetNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetNode.php', + 'Twig\\Node\\SpacelessNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SpacelessNode.php', + 'Twig\\Node\\TextNode' => __DIR__ . '/..' . '/twig/twig/src/Node/TextNode.php', + 'Twig\\Node\\WithNode' => __DIR__ . '/..' . '/twig/twig/src/Node/WithNode.php', + 'Twig\\Parser' => __DIR__ . '/..' . '/twig/twig/src/Parser.php', + 'Twig\\Profiler\\Dumper\\BaseDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BaseDumper.php', + 'Twig\\Profiler\\Dumper\\BlackfireDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php', + 'Twig\\Profiler\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/HtmlDumper.php', + 'Twig\\Profiler\\Dumper\\TextDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/TextDumper.php', + 'Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php', + 'Twig\\Profiler\\Node\\EnterProfileNode' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Node/EnterProfileNode.php', + 'Twig\\Profiler\\Node\\LeaveProfileNode' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Node/LeaveProfileNode.php', + 'Twig\\Profiler\\Profile' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Profile.php', + 'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php', + 'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php', + 'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php', + 'Twig\\Sandbox\\SecurityError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig\\Sandbox\\SecurityNotAllowedMethodError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig\\Sandbox\\SecurityNotAllowedPropertyError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig\\Sandbox\\SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php', + 'Twig\\Sandbox\\SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicy.php', + 'Twig\\Sandbox\\SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php', + 'Twig\\Source' => __DIR__ . '/..' . '/twig/twig/src/Source.php', + 'Twig\\Template' => __DIR__ . '/..' . '/twig/twig/src/Template.php', + 'Twig\\TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/src/TemplateWrapper.php', + 'Twig\\Test\\IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/IntegrationTestCase.php', + 'Twig\\Test\\NodeTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/NodeTestCase.php', + 'Twig\\Token' => __DIR__ . '/..' . '/twig/twig/src/Token.php', + 'Twig\\TokenParser\\AbstractTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AbstractTokenParser.php', + 'Twig\\TokenParser\\AutoEscapeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AutoEscapeTokenParser.php', + 'Twig\\TokenParser\\BlockTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/BlockTokenParser.php', + 'Twig\\TokenParser\\DoTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/DoTokenParser.php', + 'Twig\\TokenParser\\EmbedTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/EmbedTokenParser.php', + 'Twig\\TokenParser\\ExtendsTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', + 'Twig\\TokenParser\\FilterTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FilterTokenParser.php', + 'Twig\\TokenParser\\FlushTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FlushTokenParser.php', + 'Twig\\TokenParser\\ForTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ForTokenParser.php', + 'Twig\\TokenParser\\FromTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FromTokenParser.php', + 'Twig\\TokenParser\\IfTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IfTokenParser.php', + 'Twig\\TokenParser\\ImportTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ImportTokenParser.php', + 'Twig\\TokenParser\\IncludeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IncludeTokenParser.php', + 'Twig\\TokenParser\\MacroTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/MacroTokenParser.php', + 'Twig\\TokenParser\\SandboxTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SandboxTokenParser.php', + 'Twig\\TokenParser\\SetTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SetTokenParser.php', + 'Twig\\TokenParser\\SpacelessTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SpacelessTokenParser.php', + 'Twig\\TokenParser\\TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/TokenParserInterface.php', + 'Twig\\TokenParser\\UseTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/UseTokenParser.php', + 'Twig\\TokenParser\\WithTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/WithTokenParser.php', + 'Twig\\TokenStream' => __DIR__ . '/..' . '/twig/twig/src/TokenStream.php', + 'Twig\\TwigFilter' => __DIR__ . '/..' . '/twig/twig/src/TwigFilter.php', + 'Twig\\TwigFunction' => __DIR__ . '/..' . '/twig/twig/src/TwigFunction.php', + 'Twig\\TwigTest' => __DIR__ . '/..' . '/twig/twig/src/TwigTest.php', + 'Twig\\Util\\DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/src/Util/DeprecationCollector.php', + 'Twig\\Util\\TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/src/Util/TemplateDirIterator.php', + 'Twig_BaseNodeVisitor' => __DIR__ . '/..' . '/twig/twig/lib/Twig/BaseNodeVisitor.php', + 'Twig_CacheInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/CacheInterface.php', + 'Twig_Cache_Filesystem' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Cache/Filesystem.php', + 'Twig_Cache_Null' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Cache/Null.php', + 'Twig_Compiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Compiler.php', + 'Twig_ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ContainerRuntimeLoader.php', + 'Twig_Environment' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Environment.php', + 'Twig_Error' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error.php', + 'Twig_Error_Loader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Loader.php', + 'Twig_Error_Runtime' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Runtime.php', + 'Twig_Error_Syntax' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Syntax.php', + 'Twig_ExistsLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExistsLoaderInterface.php', + 'Twig_ExpressionParser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExpressionParser.php', + 'Twig_Extension' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension.php', + 'Twig_ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExtensionInterface.php', + 'Twig_ExtensionSet' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExtensionSet.php', + 'Twig_Extension_Core' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Core.php', + 'Twig_Extension_Debug' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Debug.php', + 'Twig_Extension_Escaper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Escaper.php', + 'Twig_Extension_GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/GlobalsInterface.php', + 'Twig_Extension_InitRuntimeInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php', + 'Twig_Extension_Optimizer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Optimizer.php', + 'Twig_Extension_Profiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Profiler.php', + 'Twig_Extension_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Sandbox.php', + 'Twig_Extension_Staging' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Staging.php', + 'Twig_Extension_StringLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/StringLoader.php', + 'Twig_FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FactoryRuntimeLoader.php', + 'Twig_FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php', + 'Twig_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter.php', + 'Twig_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function.php', + 'Twig_Lexer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Lexer.php', + 'Twig_LoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/LoaderInterface.php', + 'Twig_Loader_Array' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Array.php', + 'Twig_Loader_Chain' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Chain.php', + 'Twig_Loader_Filesystem' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Filesystem.php', + 'Twig_Markup' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Markup.php', + 'Twig_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node.php', + 'Twig_NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeCaptureInterface.php', + 'Twig_NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeOutputInterface.php', + 'Twig_NodeTraverser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeTraverser.php', + 'Twig_NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitorInterface.php', + 'Twig_NodeVisitor_Escaper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Escaper.php', + 'Twig_NodeVisitor_Optimizer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Optimizer.php', + 'Twig_NodeVisitor_SafeAnalysis' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php', + 'Twig_NodeVisitor_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Sandbox.php', + 'Twig_Node_AutoEscape' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/AutoEscape.php', + 'Twig_Node_Block' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Block.php', + 'Twig_Node_BlockReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/BlockReference.php', + 'Twig_Node_Body' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Body.php', + 'Twig_Node_CheckSecurity' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/CheckSecurity.php', + 'Twig_Node_Do' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Do.php', + 'Twig_Node_Embed' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Embed.php', + 'Twig_Node_Expression' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression.php', + 'Twig_Node_Expression_Array' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Array.php', + 'Twig_Node_Expression_AssignName' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/AssignName.php', + 'Twig_Node_Expression_Binary' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary.php', + 'Twig_Node_Expression_Binary_Add' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Add.php', + 'Twig_Node_Expression_Binary_And' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/And.php', + 'Twig_Node_Expression_Binary_BitwiseAnd' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php', + 'Twig_Node_Expression_Binary_BitwiseOr' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php', + 'Twig_Node_Expression_Binary_BitwiseXor' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php', + 'Twig_Node_Expression_Binary_Concat' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php', + 'Twig_Node_Expression_Binary_Div' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Div.php', + 'Twig_Node_Expression_Binary_EndsWith' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php', + 'Twig_Node_Expression_Binary_Equal' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php', + 'Twig_Node_Expression_Binary_FloorDiv' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php', + 'Twig_Node_Expression_Binary_Greater' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php', + 'Twig_Node_Expression_Binary_GreaterEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php', + 'Twig_Node_Expression_Binary_In' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/In.php', + 'Twig_Node_Expression_Binary_Less' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Less.php', + 'Twig_Node_Expression_Binary_LessEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php', + 'Twig_Node_Expression_Binary_Matches' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php', + 'Twig_Node_Expression_Binary_Mod' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php', + 'Twig_Node_Expression_Binary_Mul' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php', + 'Twig_Node_Expression_Binary_NotEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php', + 'Twig_Node_Expression_Binary_NotIn' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php', + 'Twig_Node_Expression_Binary_Or' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Or.php', + 'Twig_Node_Expression_Binary_Power' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Power.php', + 'Twig_Node_Expression_Binary_Range' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Range.php', + 'Twig_Node_Expression_Binary_StartsWith' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php', + 'Twig_Node_Expression_Binary_Sub' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php', + 'Twig_Node_Expression_BlockReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/BlockReference.php', + 'Twig_Node_Expression_Call' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Call.php', + 'Twig_Node_Expression_Conditional' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Conditional.php', + 'Twig_Node_Expression_Constant' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Constant.php', + 'Twig_Node_Expression_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Filter.php', + 'Twig_Node_Expression_Filter_Default' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Filter/Default.php', + 'Twig_Node_Expression_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Function.php', + 'Twig_Node_Expression_GetAttr' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/GetAttr.php', + 'Twig_Node_Expression_MethodCall' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/MethodCall.php', + 'Twig_Node_Expression_Name' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Name.php', + 'Twig_Node_Expression_NullCoalesce' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php', + 'Twig_Node_Expression_Parent' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Parent.php', + 'Twig_Node_Expression_TempName' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/TempName.php', + 'Twig_Node_Expression_Test' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test.php', + 'Twig_Node_Expression_Test_Constant' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Constant.php', + 'Twig_Node_Expression_Test_Defined' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Defined.php', + 'Twig_Node_Expression_Test_Divisibleby' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php', + 'Twig_Node_Expression_Test_Even' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Even.php', + 'Twig_Node_Expression_Test_Null' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Null.php', + 'Twig_Node_Expression_Test_Odd' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Odd.php', + 'Twig_Node_Expression_Test_Sameas' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php', + 'Twig_Node_Expression_Unary' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary.php', + 'Twig_Node_Expression_Unary_Neg' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php', + 'Twig_Node_Expression_Unary_Not' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Not.php', + 'Twig_Node_Expression_Unary_Pos' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php', + 'Twig_Node_Flush' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Flush.php', + 'Twig_Node_For' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/For.php', + 'Twig_Node_ForLoop' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/ForLoop.php', + 'Twig_Node_If' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/If.php', + 'Twig_Node_Import' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Import.php', + 'Twig_Node_Include' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Include.php', + 'Twig_Node_Macro' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Macro.php', + 'Twig_Node_Module' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Module.php', + 'Twig_Node_Print' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Print.php', + 'Twig_Node_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Sandbox.php', + 'Twig_Node_SandboxedPrint' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/SandboxedPrint.php', + 'Twig_Node_Set' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Set.php', + 'Twig_Node_Spaceless' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Spaceless.php', + 'Twig_Node_Text' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Text.php', + 'Twig_Node_With' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/With.php', + 'Twig_Parser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Parser.php', + 'Twig_Profiler_Dumper_Base' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Base.php', + 'Twig_Profiler_Dumper_Blackfire' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php', + 'Twig_Profiler_Dumper_Html' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Html.php', + 'Twig_Profiler_Dumper_Text' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Text.php', + 'Twig_Profiler_NodeVisitor_Profiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php', + 'Twig_Profiler_Node_EnterProfile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php', + 'Twig_Profiler_Node_LeaveProfile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php', + 'Twig_Profiler_Profile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Profile.php', + 'Twig_RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/RuntimeLoaderInterface.php', + 'Twig_Sandbox_SecurityError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityError.php', + 'Twig_Sandbox_SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig_Sandbox_SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig_Sandbox_SecurityNotAllowedMethodError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig_Sandbox_SecurityNotAllowedPropertyError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig_Sandbox_SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php', + 'Twig_Sandbox_SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php', + 'Twig_Sandbox_SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php', + 'Twig_SimpleFilter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleFilter.php', + 'Twig_SimpleFunction' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleFunction.php', + 'Twig_SimpleTest' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleTest.php', + 'Twig_Source' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Source.php', + 'Twig_SourceContextLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SourceContextLoaderInterface.php', + 'Twig_Template' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Template.php', + 'Twig_TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TemplateWrapper.php', + 'Twig_Test' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test.php', + 'Twig_Test_IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/IntegrationTestCase.php', + 'Twig_Test_NodeTestCase' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/NodeTestCase.php', + 'Twig_Token' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Token.php', + 'Twig_TokenParser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser.php', + 'Twig_TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserInterface.php', + 'Twig_TokenParser_AutoEscape' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/AutoEscape.php', + 'Twig_TokenParser_Block' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Block.php', + 'Twig_TokenParser_Do' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Do.php', + 'Twig_TokenParser_Embed' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Embed.php', + 'Twig_TokenParser_Extends' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Extends.php', + 'Twig_TokenParser_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Filter.php', + 'Twig_TokenParser_Flush' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Flush.php', + 'Twig_TokenParser_For' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/For.php', + 'Twig_TokenParser_From' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/From.php', + 'Twig_TokenParser_If' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/If.php', + 'Twig_TokenParser_Import' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Import.php', + 'Twig_TokenParser_Include' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Include.php', + 'Twig_TokenParser_Macro' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Macro.php', + 'Twig_TokenParser_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Sandbox.php', + 'Twig_TokenParser_Set' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Set.php', + 'Twig_TokenParser_Spaceless' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Spaceless.php', + 'Twig_TokenParser_Use' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Use.php', + 'Twig_TokenParser_With' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/With.php', + 'Twig_TokenStream' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenStream.php', + 'Twig_Util_DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/DeprecationCollector.php', + 'Twig_Util_TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php', + 'Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php', + 'WellingGuzman\\OAuth2\\Client\\Provider\\Exception\\OktaIdentityProviderException' => __DIR__ . '/..' . '/wellingguzman/oauth2-okta/src/Provider/Exception/OktaIdentityProviderException.php', + 'WellingGuzman\\OAuth2\\Client\\Provider\\Okta' => __DIR__ . '/..' . '/wellingguzman/oauth2-okta/src/Provider/Okta.php', + 'WellingGuzman\\OAuth2\\Client\\Provider\\OktaResourceOwner' => __DIR__ . '/..' . '/wellingguzman/oauth2-okta/src/Provider/OktaResourceOwner.php', + 'Zend\\Db\\Adapter\\Adapter' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Adapter.php', + 'Zend\\Db\\Adapter\\AdapterAbstractServiceFactory' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/AdapterAbstractServiceFactory.php', + 'Zend\\Db\\Adapter\\AdapterAwareInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/AdapterAwareInterface.php', + 'Zend\\Db\\Adapter\\AdapterAwareTrait' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/AdapterAwareTrait.php', + 'Zend\\Db\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/AdapterInterface.php', + 'Zend\\Db\\Adapter\\AdapterServiceFactory' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/AdapterServiceFactory.php', + 'Zend\\Db\\Adapter\\Driver\\AbstractConnection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/AbstractConnection.php', + 'Zend\\Db\\Adapter\\Driver\\ConnectionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/ConnectionInterface.php', + 'Zend\\Db\\Adapter\\Driver\\DriverInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/DriverInterface.php', + 'Zend\\Db\\Adapter\\Driver\\Feature\\AbstractFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Feature/AbstractFeature.php', + 'Zend\\Db\\Adapter\\Driver\\Feature\\DriverFeatureInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Feature/DriverFeatureInterface.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\Connection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\IbmDb2' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/IbmDb2.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\Result' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/Result.php', + 'Zend\\Db\\Adapter\\Driver\\IbmDb2\\Statement' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/IbmDb2/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Connection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Mysqli' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Mysqli.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Result' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Mysqli\\Statement' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Mysqli/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Connection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Feature\\RowCounter' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Feature/RowCounter.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Oci8' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Oci8.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Result' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Oci8\\Statement' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Oci8/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Connection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Feature\\OracleRowCounter' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Feature/OracleRowCounter.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Feature\\SqliteRowCounter' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Feature/SqliteRowCounter.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Pdo' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Pdo.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Result' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Pdo\\Statement' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pdo/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Connection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Pgsql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Pgsql.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Result' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Pgsql\\Statement' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Pgsql/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\ResultInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/ResultInterface.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Connection' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Connection.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Exception\\ErrorException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Exception/ErrorException.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Exception/ExceptionInterface.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Result' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Result.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Sqlsrv' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Sqlsrv.php', + 'Zend\\Db\\Adapter\\Driver\\Sqlsrv\\Statement' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/Sqlsrv/Statement.php', + 'Zend\\Db\\Adapter\\Driver\\StatementInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Driver/StatementInterface.php', + 'Zend\\Db\\Adapter\\Exception\\ErrorException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/ErrorException.php', + 'Zend\\Db\\Adapter\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/ExceptionInterface.php', + 'Zend\\Db\\Adapter\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/InvalidArgumentException.php', + 'Zend\\Db\\Adapter\\Exception\\InvalidConnectionParametersException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/InvalidConnectionParametersException.php', + 'Zend\\Db\\Adapter\\Exception\\InvalidQueryException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/InvalidQueryException.php', + 'Zend\\Db\\Adapter\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/RuntimeException.php', + 'Zend\\Db\\Adapter\\Exception\\UnexpectedValueException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Exception/UnexpectedValueException.php', + 'Zend\\Db\\Adapter\\ParameterContainer' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/ParameterContainer.php', + 'Zend\\Db\\Adapter\\Platform\\AbstractPlatform' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/AbstractPlatform.php', + 'Zend\\Db\\Adapter\\Platform\\IbmDb2' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/IbmDb2.php', + 'Zend\\Db\\Adapter\\Platform\\Mysql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/Mysql.php', + 'Zend\\Db\\Adapter\\Platform\\Oracle' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/Oracle.php', + 'Zend\\Db\\Adapter\\Platform\\PlatformInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/PlatformInterface.php', + 'Zend\\Db\\Adapter\\Platform\\Postgresql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/Postgresql.php', + 'Zend\\Db\\Adapter\\Platform\\Sql92' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/Sql92.php', + 'Zend\\Db\\Adapter\\Platform\\SqlServer' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/SqlServer.php', + 'Zend\\Db\\Adapter\\Platform\\Sqlite' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Platform/Sqlite.php', + 'Zend\\Db\\Adapter\\Profiler\\Profiler' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Profiler/Profiler.php', + 'Zend\\Db\\Adapter\\Profiler\\ProfilerAwareInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Profiler/ProfilerAwareInterface.php', + 'Zend\\Db\\Adapter\\Profiler\\ProfilerInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/Profiler/ProfilerInterface.php', + 'Zend\\Db\\Adapter\\StatementContainer' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/StatementContainer.php', + 'Zend\\Db\\Adapter\\StatementContainerInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Adapter/StatementContainerInterface.php', + 'Zend\\Db\\ConfigProvider' => __DIR__ . '/..' . '/zendframework/zend-db/src/ConfigProvider.php', + 'Zend\\Db\\Exception\\ErrorException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Exception/ErrorException.php', + 'Zend\\Db\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Exception/ExceptionInterface.php', + 'Zend\\Db\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Exception/InvalidArgumentException.php', + 'Zend\\Db\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Exception/RuntimeException.php', + 'Zend\\Db\\Exception\\UnexpectedValueException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Exception/UnexpectedValueException.php', + 'Zend\\Db\\Metadata\\Metadata' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Metadata.php', + 'Zend\\Db\\Metadata\\MetadataInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/MetadataInterface.php', + 'Zend\\Db\\Metadata\\Object\\AbstractTableObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/AbstractTableObject.php', + 'Zend\\Db\\Metadata\\Object\\ColumnObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/ColumnObject.php', + 'Zend\\Db\\Metadata\\Object\\ConstraintKeyObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/ConstraintKeyObject.php', + 'Zend\\Db\\Metadata\\Object\\ConstraintObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/ConstraintObject.php', + 'Zend\\Db\\Metadata\\Object\\TableObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/TableObject.php', + 'Zend\\Db\\Metadata\\Object\\TriggerObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/TriggerObject.php', + 'Zend\\Db\\Metadata\\Object\\ViewObject' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Object/ViewObject.php', + 'Zend\\Db\\Metadata\\Source\\AbstractSource' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/AbstractSource.php', + 'Zend\\Db\\Metadata\\Source\\Factory' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/Factory.php', + 'Zend\\Db\\Metadata\\Source\\MysqlMetadata' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/MysqlMetadata.php', + 'Zend\\Db\\Metadata\\Source\\OracleMetadata' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/OracleMetadata.php', + 'Zend\\Db\\Metadata\\Source\\PostgresqlMetadata' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/PostgresqlMetadata.php', + 'Zend\\Db\\Metadata\\Source\\SqlServerMetadata' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/SqlServerMetadata.php', + 'Zend\\Db\\Metadata\\Source\\SqliteMetadata' => __DIR__ . '/..' . '/zendframework/zend-db/src/Metadata/Source/SqliteMetadata.php', + 'Zend\\Db\\Module' => __DIR__ . '/..' . '/zendframework/zend-db/src/Module.php', + 'Zend\\Db\\ResultSet\\AbstractResultSet' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/AbstractResultSet.php', + 'Zend\\Db\\ResultSet\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/Exception/ExceptionInterface.php', + 'Zend\\Db\\ResultSet\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/Exception/InvalidArgumentException.php', + 'Zend\\Db\\ResultSet\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/Exception/RuntimeException.php', + 'Zend\\Db\\ResultSet\\HydratingResultSet' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/HydratingResultSet.php', + 'Zend\\Db\\ResultSet\\ResultSet' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/ResultSet.php', + 'Zend\\Db\\ResultSet\\ResultSetInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/ResultSet/ResultSetInterface.php', + 'Zend\\Db\\RowGateway\\AbstractRowGateway' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/AbstractRowGateway.php', + 'Zend\\Db\\RowGateway\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/Exception/ExceptionInterface.php', + 'Zend\\Db\\RowGateway\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/Exception/InvalidArgumentException.php', + 'Zend\\Db\\RowGateway\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/Exception/RuntimeException.php', + 'Zend\\Db\\RowGateway\\Feature\\AbstractFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/Feature/AbstractFeature.php', + 'Zend\\Db\\RowGateway\\Feature\\FeatureSet' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/Feature/FeatureSet.php', + 'Zend\\Db\\RowGateway\\RowGateway' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/RowGateway.php', + 'Zend\\Db\\RowGateway\\RowGatewayInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/RowGateway/RowGatewayInterface.php', + 'Zend\\Db\\Sql\\AbstractExpression' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/AbstractExpression.php', + 'Zend\\Db\\Sql\\AbstractPreparableSql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/AbstractPreparableSql.php', + 'Zend\\Db\\Sql\\AbstractSql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/AbstractSql.php', + 'Zend\\Db\\Sql\\Combine' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Combine.php', + 'Zend\\Db\\Sql\\Ddl\\AlterTable' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/AlterTable.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\AbstractLengthColumn' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/AbstractLengthColumn.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\AbstractPrecisionColumn' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/AbstractPrecisionColumn.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\AbstractTimestampColumn' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/AbstractTimestampColumn.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\BigInteger' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/BigInteger.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Binary' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Binary.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Blob' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Blob.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Boolean' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Boolean.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Char' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Char.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Column' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Column.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\ColumnInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/ColumnInterface.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Date' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Date.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Datetime' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Datetime.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Decimal' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Decimal.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Float' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Float.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Floating' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Floating.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Integer' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Integer.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Text' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Text.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Time' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Time.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Timestamp' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Timestamp.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Varbinary' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Varbinary.php', + 'Zend\\Db\\Sql\\Ddl\\Column\\Varchar' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Column/Varchar.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\AbstractConstraint' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Constraint/AbstractConstraint.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\Check' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Constraint/Check.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Constraint/ConstraintInterface.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\ForeignKey' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Constraint/ForeignKey.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\PrimaryKey' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Constraint/PrimaryKey.php', + 'Zend\\Db\\Sql\\Ddl\\Constraint\\UniqueKey' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Constraint/UniqueKey.php', + 'Zend\\Db\\Sql\\Ddl\\CreateTable' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/CreateTable.php', + 'Zend\\Db\\Sql\\Ddl\\DropTable' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/DropTable.php', + 'Zend\\Db\\Sql\\Ddl\\Index\\AbstractIndex' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Index/AbstractIndex.php', + 'Zend\\Db\\Sql\\Ddl\\Index\\Index' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/Index/Index.php', + 'Zend\\Db\\Sql\\Ddl\\SqlInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Ddl/SqlInterface.php', + 'Zend\\Db\\Sql\\Delete' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Delete.php', + 'Zend\\Db\\Sql\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Exception/ExceptionInterface.php', + 'Zend\\Db\\Sql\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Exception/InvalidArgumentException.php', + 'Zend\\Db\\Sql\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Exception/RuntimeException.php', + 'Zend\\Db\\Sql\\Expression' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Expression.php', + 'Zend\\Db\\Sql\\ExpressionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/ExpressionInterface.php', + 'Zend\\Db\\Sql\\Having' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Having.php', + 'Zend\\Db\\Sql\\Insert' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Insert.php', + 'Zend\\Db\\Sql\\Join' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Join.php', + 'Zend\\Db\\Sql\\Literal' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Literal.php', + 'Zend\\Db\\Sql\\Platform\\AbstractPlatform' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/AbstractPlatform.php', + 'Zend\\Db\\Sql\\Platform\\IbmDb2\\IbmDb2' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/IbmDb2/IbmDb2.php', + 'Zend\\Db\\Sql\\Platform\\IbmDb2\\SelectDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/IbmDb2/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\Ddl\\AlterTableDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Mysql/Ddl/AlterTableDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\Ddl\\CreateTableDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Mysql/Ddl/CreateTableDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\Mysql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Mysql/Mysql.php', + 'Zend\\Db\\Sql\\Platform\\Mysql\\SelectDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Mysql/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Oracle\\Oracle' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Oracle/Oracle.php', + 'Zend\\Db\\Sql\\Platform\\Oracle\\SelectDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Oracle/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Platform' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Platform.php', + 'Zend\\Db\\Sql\\Platform\\PlatformDecoratorInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/PlatformDecoratorInterface.php', + 'Zend\\Db\\Sql\\Platform\\SqlServer\\Ddl\\CreateTableDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/SqlServer/Ddl/CreateTableDecorator.php', + 'Zend\\Db\\Sql\\Platform\\SqlServer\\SelectDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/SqlServer/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\SqlServer\\SqlServer' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/SqlServer/SqlServer.php', + 'Zend\\Db\\Sql\\Platform\\Sqlite\\SelectDecorator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Sqlite/SelectDecorator.php', + 'Zend\\Db\\Sql\\Platform\\Sqlite\\Sqlite' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Platform/Sqlite/Sqlite.php', + 'Zend\\Db\\Sql\\Predicate\\Between' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/Between.php', + 'Zend\\Db\\Sql\\Predicate\\Expression' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/Expression.php', + 'Zend\\Db\\Sql\\Predicate\\In' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/In.php', + 'Zend\\Db\\Sql\\Predicate\\IsNotNull' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/IsNotNull.php', + 'Zend\\Db\\Sql\\Predicate\\IsNull' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/IsNull.php', + 'Zend\\Db\\Sql\\Predicate\\Like' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/Like.php', + 'Zend\\Db\\Sql\\Predicate\\Literal' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/Literal.php', + 'Zend\\Db\\Sql\\Predicate\\NotBetween' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/NotBetween.php', + 'Zend\\Db\\Sql\\Predicate\\NotIn' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/NotIn.php', + 'Zend\\Db\\Sql\\Predicate\\NotLike' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/NotLike.php', + 'Zend\\Db\\Sql\\Predicate\\Operator' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/Operator.php', + 'Zend\\Db\\Sql\\Predicate\\Predicate' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/Predicate.php', + 'Zend\\Db\\Sql\\Predicate\\PredicateInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/PredicateInterface.php', + 'Zend\\Db\\Sql\\Predicate\\PredicateSet' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Predicate/PredicateSet.php', + 'Zend\\Db\\Sql\\PreparableSqlInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/PreparableSqlInterface.php', + 'Zend\\Db\\Sql\\Select' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Select.php', + 'Zend\\Db\\Sql\\Sql' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Sql.php', + 'Zend\\Db\\Sql\\SqlInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/SqlInterface.php', + 'Zend\\Db\\Sql\\TableIdentifier' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/TableIdentifier.php', + 'Zend\\Db\\Sql\\Update' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Update.php', + 'Zend\\Db\\Sql\\Where' => __DIR__ . '/..' . '/zendframework/zend-db/src/Sql/Where.php', + 'Zend\\Db\\TableGateway\\AbstractTableGateway' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/AbstractTableGateway.php', + 'Zend\\Db\\TableGateway\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Exception/ExceptionInterface.php', + 'Zend\\Db\\TableGateway\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Exception/InvalidArgumentException.php', + 'Zend\\Db\\TableGateway\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Exception/RuntimeException.php', + 'Zend\\Db\\TableGateway\\Feature\\AbstractFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/AbstractFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\EventFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/EventFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\EventFeatureEventsInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/EventFeatureEventsInterface.php', + 'Zend\\Db\\TableGateway\\Feature\\EventFeature\\TableGatewayEvent' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php', + 'Zend\\Db\\TableGateway\\Feature\\FeatureSet' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/FeatureSet.php', + 'Zend\\Db\\TableGateway\\Feature\\GlobalAdapterFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/GlobalAdapterFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\MasterSlaveFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/MasterSlaveFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\MetadataFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/MetadataFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\RowGatewayFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/RowGatewayFeature.php', + 'Zend\\Db\\TableGateway\\Feature\\SequenceFeature' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/Feature/SequenceFeature.php', + 'Zend\\Db\\TableGateway\\TableGateway' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/TableGateway.php', + 'Zend\\Db\\TableGateway\\TableGatewayInterface' => __DIR__ . '/..' . '/zendframework/zend-db/src/TableGateway/TableGatewayInterface.php', + 'Zend\\Stdlib\\AbstractOptions' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/AbstractOptions.php', + 'Zend\\Stdlib\\ArrayObject' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayObject.php', + 'Zend\\Stdlib\\ArraySerializableInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArraySerializableInterface.php', + 'Zend\\Stdlib\\ArrayStack' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayStack.php', + 'Zend\\Stdlib\\ArrayUtils' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeRemoveKey' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKey' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Zend\\Stdlib\\ConsoleHelper' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ConsoleHelper.php', + 'Zend\\Stdlib\\DispatchableInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/DispatchableInterface.php', + 'Zend\\Stdlib\\ErrorHandler' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ErrorHandler.php', + 'Zend\\Stdlib\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/BadMethodCallException.php', + 'Zend\\Stdlib\\Exception\\DomainException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/DomainException.php', + 'Zend\\Stdlib\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/ExceptionInterface.php', + 'Zend\\Stdlib\\Exception\\ExtensionNotLoadedException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Zend\\Stdlib\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/InvalidArgumentException.php', + 'Zend\\Stdlib\\Exception\\LogicException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/LogicException.php', + 'Zend\\Stdlib\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/RuntimeException.php', + 'Zend\\Stdlib\\FastPriorityQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/FastPriorityQueue.php', + 'Zend\\Stdlib\\Glob' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Glob.php', + 'Zend\\Stdlib\\Guard\\AllGuardsTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php', + 'Zend\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Zend\\Stdlib\\Guard\\EmptyGuardTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/EmptyGuardTrait.php', + 'Zend\\Stdlib\\Guard\\NullGuardTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/NullGuardTrait.php', + 'Zend\\Stdlib\\InitializableInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/InitializableInterface.php', + 'Zend\\Stdlib\\JsonSerializable' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/JsonSerializable.php', + 'Zend\\Stdlib\\Message' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Message.php', + 'Zend\\Stdlib\\MessageInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/MessageInterface.php', + 'Zend\\Stdlib\\ParameterObjectInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ParameterObjectInterface.php', + 'Zend\\Stdlib\\Parameters' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Parameters.php', + 'Zend\\Stdlib\\ParametersInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ParametersInterface.php', + 'Zend\\Stdlib\\PriorityList' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/PriorityList.php', + 'Zend\\Stdlib\\PriorityQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/PriorityQueue.php', + 'Zend\\Stdlib\\Request' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Request.php', + 'Zend\\Stdlib\\RequestInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/RequestInterface.php', + 'Zend\\Stdlib\\Response' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Response.php', + 'Zend\\Stdlib\\ResponseInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ResponseInterface.php', + 'Zend\\Stdlib\\SplPriorityQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/SplPriorityQueue.php', + 'Zend\\Stdlib\\SplQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/SplQueue.php', + 'Zend\\Stdlib\\SplStack' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/SplStack.php', + 'Zend\\Stdlib\\StringUtils' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringUtils.php', + 'Zend\\Stdlib\\StringWrapper\\AbstractStringWrapper' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Zend\\Stdlib\\StringWrapper\\Iconv' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/Iconv.php', + 'Zend\\Stdlib\\StringWrapper\\Intl' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/Intl.php', + 'Zend\\Stdlib\\StringWrapper\\MbString' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/MbString.php', + 'Zend\\Stdlib\\StringWrapper\\Native' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/Native.php', + 'Zend\\Stdlib\\StringWrapper\\StringWrapperInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'phpDocumentor\\Reflection\\DocBlock' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock.php', + 'phpDocumentor\\Reflection\\DocBlockFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php', + 'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php', + 'phpDocumentor\\Reflection\\DocBlock\\Description' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php', + 'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php', + 'phpDocumentor\\Reflection\\DocBlock\\Serializer' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php', + 'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php', + 'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\Strategy' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php', + 'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php', + 'phpDocumentor\\Reflection\\Element' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Element.php', + 'phpDocumentor\\Reflection\\File' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/File.php', + 'phpDocumentor\\Reflection\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Fqsen.php', + 'phpDocumentor\\Reflection\\FqsenResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/FqsenResolver.php', + 'phpDocumentor\\Reflection\\Location' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Location.php', + 'phpDocumentor\\Reflection\\Project' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Project.php', + 'phpDocumentor\\Reflection\\ProjectFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/ProjectFactory.php', + 'phpDocumentor\\Reflection\\Type' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Type.php', + 'phpDocumentor\\Reflection\\TypeResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/TypeResolver.php', + 'phpDocumentor\\Reflection\\Types\\Array_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Array_.php', + 'phpDocumentor\\Reflection\\Types\\Boolean' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Boolean.php', + 'phpDocumentor\\Reflection\\Types\\Callable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Callable_.php', + 'phpDocumentor\\Reflection\\Types\\Compound' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Compound.php', + 'phpDocumentor\\Reflection\\Types\\Context' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Context.php', + 'phpDocumentor\\Reflection\\Types\\ContextFactory' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php', + 'phpDocumentor\\Reflection\\Types\\Float_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Float_.php', + 'phpDocumentor\\Reflection\\Types\\Integer' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Integer.php', + 'phpDocumentor\\Reflection\\Types\\Iterable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Iterable_.php', + 'phpDocumentor\\Reflection\\Types\\Mixed_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Mixed_.php', + 'phpDocumentor\\Reflection\\Types\\Null_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Null_.php', + 'phpDocumentor\\Reflection\\Types\\Nullable' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Nullable.php', + 'phpDocumentor\\Reflection\\Types\\Object_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Object_.php', + 'phpDocumentor\\Reflection\\Types\\Parent_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Parent_.php', + 'phpDocumentor\\Reflection\\Types\\Resource_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Resource_.php', + 'phpDocumentor\\Reflection\\Types\\Scalar' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Scalar.php', + 'phpDocumentor\\Reflection\\Types\\Self_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Self_.php', + 'phpDocumentor\\Reflection\\Types\\Static_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Static_.php', + 'phpDocumentor\\Reflection\\Types\\String_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/String_.php', + 'phpDocumentor\\Reflection\\Types\\This' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/This.php', + 'phpDocumentor\\Reflection\\Types\\Void_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Void_.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit989a4969ed6a6e584179ae2090c4eecd::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit989a4969ed6a6e584179ae2090c4eecd::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit989a4969ed6a6e584179ae2090c4eecd::$prefixesPsr0; + $loader->classMap = ComposerStaticInit989a4969ed6a6e584179ae2090c4eecd::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000000..378218e446 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,3929 @@ +[ + { + "name": "psr/log", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-10-10T12:19:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "monolog/monolog", + "version": "1.23.0", + "version_normalized": "1.23.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "time": "2017-06-19T01:22:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "zendframework/zend-stdlib", + "version": "3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "cd164b4a18b5d1aeb69be2c26db035b5ed6925ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cd164b4a18b5d1aeb69be2c26db035b5ed6925ae", + "reference": "cd164b4a18b5d1aeb69be2c26db035b5ed6925ae", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "time": "2018-04-30T13:50:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "keywords": [ + "ZendFramework", + "stdlib", + "zf" + ] + }, + { + "name": "zendframework/zend-db", + "version": "dev-directus", + "version_normalized": "dev-directus", + "source": { + "type": "git", + "url": "https://github.com/wellingguzman/zend-db", + "reference": "ef55371343a85e5cd0937d0ab9d30da7e86f5ab3" + }, + "require": { + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.25 || ^6.4.4", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", + "zendframework/zend-hydrator": "^1.1 || ^2.1", + "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + }, + "suggest": { + "zendframework/zend-eventmanager": "Zend\\EventManager component", + "zendframework/zend-hydrator": "Zend\\Hydrator component for using HydratingResultSets", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + }, + "time": "2018-04-05T21:56:49+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.9-dev", + "dev-develop": "2.10-dev" + }, + "zf": { + "component": "Zend\\Db", + "config-provider": "Zend\\Db\\ConfigProvider" + } + }, + "installation-source": "source", + "autoload": { + "psr-4": { + "Zend\\Db\\": "src/" + } + }, + "autoload-dev": { + "files": [ + "test/autoload.php" + ], + "psr-4": { + "ZendTest\\Db\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": [ + "phpcs" + ], + "cs-fix": [ + "phpcbf" + ], + "test": [ + "phpunit --colors=always" + ], + "test-coverage": [ + "phpunit --colors=always --coverage-clover clover.xml" + ], + "upload-coverage": [ + "coveralls -v" + ] + }, + "license": [ + "BSD-3-Clause" + ], + "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "keywords": [ + "db", + "zendframework", + "zf" + ], + "support": { + "docs": "https://docs.zendframework.com/zend-db/", + "issues": "https://github.com/zendframework/zend-db/issues", + "source": "https://github.com/zendframework/zend-db", + "rss": "https://github.com/zendframework/zend-db/releases.atom", + "slack": "https://zendframework-slack.herokuapp.com", + "forum": "https://discourse.zendframework.com/c/questions/components" + } + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.15", + "version_normalized": "2.0.15.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", + "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." + }, + "time": "2018-06-08T15:26:40+00:00", + "type": "library", + "installation-source": "dist", + "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", + "polyfill", + "pseudorandom", + "random" + ] + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "time": "2016-12-20T10:07:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ] + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "version_normalized": "1.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2017-03-20T17:10:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ] + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "version_normalized": "6.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "time": "2018-04-22T15:46:56+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + }, + { + "name": "league/oauth2-client", + "version": "2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "aa2e3df188f0bfd87f7880cc880e906e99923580" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/aa2e3df188f0bfd87f7880cc880e906e99923580", + "reference": "aa2e3df188f0bfd87f7880cc880e906e99923580", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "paragonie/random_compat": "^1|^2", + "php": "^5.6|^7.0" + }, + "require-dev": { + "eloquent/liberator": "^2.0", + "eloquent/phony-phpunit": "^1.0|^3.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phpunit/phpunit": "^5.7|^6.0", + "squizlabs/php_codesniffer": "^2.3|^3.0" + }, + "time": "2018-01-13T05:27:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ] + }, + { + "name": "league/oauth2-github", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-github.git", + "reference": "e63d64f3ec167c09232d189c6b0c397458a99357" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-github/zipball/e63d64f3ec167c09232d189c6b0c397458a99357", + "reference": "e63d64f3ec167c09232d189c6b0c397458a99357", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "time": "2017-01-26T01:14:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "description": "Github OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "github", + "oauth", + "oauth2" + ] + }, + { + "name": "league/oauth1-client", + "version": "1.7.0", + "version_normalized": "1.7.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/fca5f160650cb74d23fc11aa570dd61f86dcf647", + "reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0" + }, + "require-dev": { + "mockery/mockery": "^0.9", + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "time": "2016-08-17T00:36:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth1\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "description": "OAuth 1.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "bitbucket", + "identity", + "idp", + "oauth", + "oauth1", + "single sign on", + "trello", + "tumblr", + "twitter" + ] + }, + { + "name": "wellingguzman/oauth2-okta", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/WellingGuzman/oauth2-okta.git", + "reference": "5de9ef704ba079d913f75a40a98ecd61958860a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WellingGuzman/oauth2-okta/zipball/5de9ef704ba079d913f75a40a98ecd61958860a5", + "reference": "5de9ef704ba079d913f75a40a98ecd61958860a5", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "time": "2018-04-16T18:17:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.0.x-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-4": { + "WellingGuzman\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Welling Guzman", + "email": "wellingguzman@gmail.com", + "homepage": "https://github.com/wellingguzman" + } + ], + "description": "Okta OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "oauth", + "oauth2", + "okta" + ] + }, + { + "name": "league/flysystem", + "version": "1.0.45", + "version_normalized": "1.0.45.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a99f94e63b512d75f851b181afcdf0ee9ebef7e6", + "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "ext-fileinfo": "*", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "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-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", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "time": "2018-05-07T08:44:23+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ] + }, + { + "name": "doctrine/cache", + "version": "v1.7.1", + "version_normalized": "1.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a", + "shasum": "" + }, + "require": { + "php": "~7.1" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "time": "2017-08-25T07:02:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ] + }, + { + "name": "psr/cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T20:24:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ] + }, + { + "name": "cache/cache", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/cache.git", + "reference": "ca3bd08ebe53f5b13b5c4f589d57b1fe97da001c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/cache/zipball/ca3bd08ebe53f5b13b5c4f589d57b1fe97da001c", + "reference": "ca3bd08ebe53f5b13b5c4f589d57b1fe97da001c", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.3", + "league/flysystem": "^1.0", + "php": "^5.6 || ^7.0", + "psr/cache": "^1.0", + "psr/log": "^1.0", + "psr/simple-cache": "^1.0" + }, + "conflict": { + "cache/adapter-common": "*", + "cache/apc-adapter": "*", + "cache/apcu-adapter": "*", + "cache/array-adapter": "*", + "cache/chain-adapter": "*", + "cache/doctrine-adapter": "*", + "cache/filesystem-adapter": "*", + "cache/hierarchical-cache": "*", + "cache/illuminate-adapter": "*", + "cache/memcache-adapter": "*", + "cache/memcached-adapter": "*", + "cache/mongodb-adapter": "*", + "cache/predis-adapter": "*", + "cache/psr-6-doctrine-bridge": "*", + "cache/redis-adapter": "*", + "cache/session-handler": "*", + "cache/taggable-cache": "*", + "cache/void-adapter": "*" + }, + "require-dev": { + "cache/integration-tests": "^0.16", + "defuse/php-encryption": "^2.0", + "illuminate/cache": "^5.4", + "mockery/mockery": "^0.9.9", + "phpunit/phpunit": "^5.7.21", + "predis/predis": "^1.1", + "symfony/cache": "^3.1" + }, + "suggest": { + "ext-apc": "APC extension is required to use the APC Adapter", + "ext-apcu": "APCu extension is required to use the APCu Adapter", + "ext-memcache": "Memcache extension is required to use the Memcache Adapter", + "ext-memcached": "Memcached extension is required to use the Memcached Adapter", + "ext-mongodb": "Mongodb extension required to use the Mongodb adapter", + "ext-redis": "Redis extension is required to use the Redis adapter", + "mongodb/mongodb": "Mongodb lib required to use the Mongodb adapter" + }, + "time": "2017-07-17T11:15:08+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cache\\": "src/" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "Library of all the php-cache adapters", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr6" + ] + }, + { + "name": "akrabat/rka-ip-address-middleware", + "version": "0.5", + "version_normalized": "0.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/akrabat/rka-ip-address-middleware.git", + "reference": "832687b13bae4d7fe889fab1414aef6fafeecf80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/akrabat/rka-ip-address-middleware/zipball/832687b13bae4d7fe889fab1414aef6fafeecf80", + "reference": "832687b13bae4d7fe889fab1414aef6fafeecf80", + "shasum": "" + }, + "require": { + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "squizlabs/php_codesniffer": "^2.3", + "zendframework/zend-diactoros": "^1.1" + }, + "time": "2016-11-13T12:23:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "RKA\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + } + ], + "description": "PSR-7 Middleware that determines the client IP address and stores it as an ServerRequest attribute", + "homepage": "http://github.com/akrabat/rka-ip-address-middleware", + "keywords": [ + "IP", + "middleware", + "psr7" + ], + "abandoned": "akrabat/ip-address-middleware" + }, + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "version_normalized": "5.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "time": "2017-06-27T22:17:23+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2018-04-30T19:57:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ] + }, + { + "name": "symfony/yaml", + "version": "v4.1.0", + "version_normalized": "4.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "time": "2018-05-30T07:26:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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 Yaml Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "3296adf6a6454a050679cde90f95350ad604b171" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2018-04-26T10:06:28+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "symfony/console", + "version": "v4.1.0", + "version_normalized": "4.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", + "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "time": "2018-05-30T07:26:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "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 Console Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/filesystem", + "version": "v4.1.0", + "version_normalized": "4.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "time": "2018-05-30T07:26:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "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 Filesystem Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/config", + "version": "v4.1.0", + "version_normalized": "4.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", + "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "time": "2018-05-16T14:33:22+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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 Config Component", + "homepage": "https://symfony.com" + }, + { + "name": "robmorgan/phinx", + "version": "0.9.2", + "version_normalized": "0.9.2.0", + "source": { + "type": "git", + "url": "https://github.com/cakephp/phinx.git", + "reference": "e1698319ad55157c233b658c08f7a10617e797ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cakephp/phinx/zipball/e1698319ad55157c233b658c08f7a10617e797ca", + "reference": "e1698319ad55157c233b658c08f7a10617e797ca", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "symfony/config": "^2.8|^3.0|^4.0", + "symfony/console": "^2.8|^3.0|^4.0", + "symfony/yaml": "^2.8|^3.0|^4.0" + }, + "require-dev": { + "cakephp/cakephp-codesniffer": "^3.0", + "phpunit/phpunit": "^4.8.35|^5.7|^6.5" + }, + "time": "2017-12-23T06:48:51+00:00", + "bin": [ + "bin/phinx" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me", + "role": "Developer" + }, + { + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "https://robmorgan.id.au", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Developer" + }, + { + "name": "CakePHP Community", + "homepage": "https://github.com/cakephp/phinx/graphs/contributors" + } + ], + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "homepage": "https://phinx.org", + "keywords": [ + "database", + "database migrations", + "db", + "migrations", + "phinx" + ] + }, + { + "name": "wellingguzman/rate-limit", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/WellingGuzman/rate-limit.git", + "reference": "4930e8715f820452c3c2f2f6375533e6863dd669" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WellingGuzman/rate-limit/zipball/4930e8715f820452c3c2f2f6375533e6863dd669", + "reference": "4930e8715f820452c3c2f2f6375533e6863dd669", + "shasum": "" + }, + "require": { + "php": "^5.6 | ^7.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "phpunit/phpunit": "^4.7 | ^5.0", + "zendframework/zend-diactoros": "^1.3" + }, + "time": "2018-06-15T22:46:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-4": { + "RateLimit\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikola Poša", + "email": "posa.nikola@gmail.com", + "homepage": "http://www.nikolaposa.in.rs" + }, + { + "name": "Welling Guzmán", + "email": "hola@wellingguzman.com", + "homepage": "http://wellingguzman.com" + } + ], + "description": "Standalone component that facilitates rate-limiting functionality. Also provides a middleware designed for API and/or other application endpoints.", + "keywords": [ + "middleware", + "rate limit" + ] + }, + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14T16:28:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ] + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "version_normalized": "1.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "time": "2017-02-14T19:40:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "time": "2018-02-13T20:26:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ] + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "version_normalized": "3.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "time": "2018-01-21T07:42:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ] + }, + { + "name": "slim/slim", + "version": "3.10.0", + "version_normalized": "3.10.0.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748", + "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "nikic/fast-route": "^1.0", + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/container": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "time": "2018-04-19T19:29:08+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ] + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.9", + "version_normalized": "5.4.9.0", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "7ffc1ea296ed14bf8260b6ef11b80208dbadba91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/7ffc1ea296ed14bf8260b6ef11b80208dbadba91", + "reference": "7ffc1ea296ed14bf8260b6ef11b80208dbadba91", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "time": "2018-01-23T07:37:21+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ] + }, + { + "name": "league/oauth2-google", + "version": "2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-google.git", + "reference": "c0faed29ec6d665ce3234e01f62029516cee4c02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/c0faed29ec6d665ce3234e01f62029516cee4c02", + "reference": "c0faed29ec6d665ce3234e01f62029516cee4c02", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony": "^0.14.6", + "phpunit/phpunit": "^5.7", + "satooshi/php-coveralls": "^2.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "time": "2018-03-19T17:28:55+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "google", + "oauth", + "oauth2" + ] + }, + { + "name": "league/oauth2-facebook", + "version": "2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-facebook.git", + "reference": "bcbcd540fb66ae16b4f82671c8ae7752b6a89556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-facebook/zipball/bcbcd540fb66ae16b4f82671c8ae7752b6a89556", + "reference": "bcbcd540fb66ae16b4f82671c8ae7752b6a89556", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "time": "2017-07-22T01:25:00+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sammy Kaye Powers", + "email": "me@sammyk.me", + "homepage": "http://www.sammyk.me" + } + ], + "description": "Facebook OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "facebook", + "oauth", + "oauth2" + ] + }, + { + "name": "intervention/image", + "version": "2.4.2", + "version_normalized": "2.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb", + "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "time": "2018-05-29T14:19:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@olivervogel.com", + "homepage": "http://olivervogel.com/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ] + }, + { + "name": "symfony/translation", + "version": "v4.1.0", + "version_normalized": "4.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", + "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "time": "2018-05-30T07:26:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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 Translation Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/validator", + "version": "v3.4.11", + "version_normalized": "3.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "62ccdf62042fbca3be1e7b2dae84965940023362" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/62ccdf62042fbca3be1e7b2dae84965940023362", + "reference": "62ccdf62042fbca3be1e7b2dae84965940023362", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation": "~2.8|~3.0|~4.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.3", + "symfony/http-kernel": "<3.3.5", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "egulias/email-validator": "^1.2.8|~2.0", + "symfony/cache": "~3.1|~4.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "^3.3.5|~4.0", + "symfony/intl": "^2.8.18|^3.2.5|~4.0", + "symfony/property-access": "~2.8|~3.0|~4.0", + "symfony/var-dumper": "~3.3|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", + "doctrine/cache": "For using the default cached annotation reader and metadata cache.", + "egulias/email-validator": "Strict (RFC compliant) email validation", + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "", + "symfony/expression-language": "For using the Expression validator", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For accessing properties within comparison constraints", + "symfony/yaml": "" + }, + "time": "2018-05-18T02:00:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "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 Validator Component", + "homepage": "https://symfony.com" + }, + { + "name": "twig/twig", + "version": "v2.4.8", + "version_normalized": "2.4.8.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "7b604c89da162034bdf4bb66310f358d313dd16d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7b604c89da162034bdf4bb66310f358d313dd16d", + "reference": "7b604c89da162034bdf4bb66310f358d313dd16d", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.3" + }, + "time": "2018-04-02T09:24:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ] + }, + { + "name": "slim/twig-view", + "version": "2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Twig-View.git", + "reference": "78386c01a97f7870462b38fff759dad649da9efc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/78386c01a97f7870462b38fff759dad649da9efc", + "reference": "78386c01a97f7870462b38fff759dad649da9efc", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "twig/twig": "^1.18|^2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "slim/slim": "^3.10" + }, + "time": "2018-05-07T10:54:29+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 view helper built on top of the Twig 2 templating component", + "homepage": "http://slimframework.com", + "keywords": [ + "framework", + "slim", + "template", + "twig", + "view" + ] + }, + { + "name": "ramsey/uuid", + "version": "3.7.3", + "version_normalized": "3.7.3.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0", + "php": "^5.4 || ^7.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "time": "2018-01-20T00:28:24+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ] + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "time": "2016-10-03T07:35:21+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "time": "2015-07-28T20:34:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "time": "2016-11-19T07:33:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "time": "2017-02-18T15:18:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "time": "2015-10-12T03:26:01+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ] + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "time": "2016-11-19T08:54:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ] + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "time": "2016-11-26T07:53:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ] + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "time": "2017-05-22T07:24:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ] + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "version_normalized": "1.2.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "time": "2017-01-29T09:50:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ] + }, + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "time": "2017-07-22T11:58:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ] + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "version_normalized": "1.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2015-06-21T13:50:34+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ] + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "version_normalized": "3.4.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "time": "2017-06-30T09:13:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ] + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "version_normalized": "1.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "time": "2017-02-26T11:10:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ] + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "version_normalized": "1.4.5.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2017-11-27T13:52:08+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ] + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "time": "2017-03-04T06:30:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "time": "2017-11-27T05:48:46+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ] + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "version_normalized": "4.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "time": "2017-04-02T07:44:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ] + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "time": "2018-01-29T19:49:41+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "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" + ] + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "time": "2017-09-11T18:02:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ] + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "version_normalized": "0.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "time": "2017-07-14T14:27:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ] + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "version_normalized": "4.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "time": "2017-11-30T07:14:17+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock." + }, + { + "name": "phpspec/prophecy", + "version": "1.7.6", + "version_normalized": "1.7.6.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "time": "2018-04-18T13:57:24+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ] + }, + { + "name": "myclabs/deep-copy", + "version": "1.8.1", + "version_normalized": "1.8.1.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "time": "2018-06-11T23:09:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ] + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "version_normalized": "5.7.27.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "time": "2018-02-01T05:50:59+00:00", + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ] + } +] diff --git a/vendor/container-interop/container-interop/LICENSE b/vendor/container-interop/container-interop/LICENSE new file mode 100644 index 0000000000..7671d9020f --- /dev/null +++ b/vendor/container-interop/container-interop/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 container-interop + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/container-interop/container-interop/README.md b/vendor/container-interop/container-interop/README.md new file mode 100644 index 0000000000..cdd7a44c8e --- /dev/null +++ b/vendor/container-interop/container-interop/README.md @@ -0,0 +1,148 @@ +# Container Interoperability + +[![Latest Stable Version](https://poser.pugx.org/container-interop/container-interop/v/stable.png)](https://packagist.org/packages/container-interop/container-interop) +[![Total Downloads](https://poser.pugx.org/container-interop/container-interop/downloads.svg)](https://packagist.org/packages/container-interop/container-interop) + +## Deprecation warning! + +Starting Feb. 13th 2017, container-interop is officially deprecated in favor of [PSR-11](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md). +Container-interop has been the test-bed of PSR-11. From v1.2, container-interop directly extends PSR-11 interfaces. +Therefore, all containers implementing container-interop are now *de-facto* compatible with PSR-11. + +- Projects implementing container-interop interfaces are encouraged to directly implement PSR-11 interfaces instead. +- Projects consuming container-interop interfaces are very strongly encouraged to directly type-hint on PSR-11 interfaces, in order to be compatible with PSR-11 containers that are not compatible with container-interop. + +Regarding the delegate lookup feature, that is present in container-interop and not in PSR-11, the feature is actually a design pattern. It is therefore not deprecated. Documentation regarding this design pattern will be migrated from this repository into a separate website in the future. + +## About + +*container-interop* tries to identify and standardize features in *container* objects (service locators, +dependency injection containers, etc.) to achieve interoperability. + +Through discussions and trials, we try to create a standard, made of common interfaces but also recommendations. + +If PHP projects that provide container implementations begin to adopt these common standards, then PHP +applications and projects that use containers can depend on the common interfaces instead of specific +implementations. This facilitates a high-level of interoperability and flexibility that allows users to consume +*any* container implementation that can be adapted to these interfaces. + +The work done in this project is not officially endorsed by the [PHP-FIG](http://www.php-fig.org/), but it is being +worked on by members of PHP-FIG and other good developers. We adhere to the spirit and ideals of PHP-FIG, and hope +this project will pave the way for one or more future PSRs. + + +## Installation + +You can install this package through Composer: + +```json +composer require container-interop/container-interop +``` + +The packages adheres to the [SemVer](http://semver.org/) specification, and there will be full backward compatibility +between minor versions. + +## Standards + +### Available + +- [`ContainerInterface`](src/Interop/Container/ContainerInterface.php). +[Description](docs/ContainerInterface.md) [Meta Document](docs/ContainerInterface-meta.md). +Describes the interface of a container that exposes methods to read its entries. +- [*Delegate lookup feature*](docs/Delegate-lookup.md). +[Meta Document](docs/Delegate-lookup-meta.md). +Describes the ability for a container to delegate the lookup of its dependencies to a third-party container. This +feature lets several containers work together in a single application. + +### Proposed + +View open [request for comments](https://github.com/container-interop/container-interop/labels/RFC) + +## Compatible projects + +### Projects implementing `ContainerInterface` + +- [Acclimate](https://github.com/jeremeamia/acclimate-container): Adapters for + Aura.Di, Laravel, Nette DI, Pimple, Symfony DI, ZF2 Service manager, ZF2 + Dependency injection and any container using `ArrayAccess` +- [Aura.Di](https://github.com/auraphp/Aura.Di) +- [auryn-container-interop](https://github.com/elazar/auryn-container-interop) +- [Burlap](https://github.com/codeeverything/burlap) +- [Chernozem](https://github.com/pyrsmk/Chernozem) +- [Data Manager](https://github.com/chrismichaels84/data-manager) +- [Disco](https://github.com/bitexpert/disco) +- [InDI](https://github.com/idealogica/indi) +- [League/Container](http://container.thephpleague.com/) +- [Mouf](http://mouf-php.com) +- [Njasm Container](https://github.com/njasm/container) +- [PHP-DI](http://php-di.org) +- [Picotainer](https://github.com/thecodingmachine/picotainer) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) +- [Pimple3-ContainerInterop](https://github.com/Sam-Burns/pimple3-containerinterop) (using Pimple v3) +- [SitePoint Container](https://github.com/sitepoint/Container) +- [Thruster Container](https://github.com/ThrusterIO/container) (PHP7 only) +- [Ultra-Lite Container](https://github.com/ultra-lite/container) +- [Unbox](https://github.com/mindplay-dk/unbox) +- [XStatic](https://github.com/jeremeamia/xstatic) +- [Zend\ServiceManager](https://github.com/zendframework/zend-servicemanager) +- [Zit](https://github.com/inxilpro/Zit) + +### Projects implementing the *delegate lookup* feature + +- [Aura.Di](https://github.com/auraphp/Aura.Di) +- [Burlap](https://github.com/codeeverything/burlap) +- [Chernozem](https://github.com/pyrsmk/Chernozem) +- [InDI](https://github.com/idealogica/indi) +- [League/Container](http://container.thephpleague.com/) +- [Mouf](http://mouf-php.com) +- [Picotainer](https://github.com/thecodingmachine/picotainer) +- [PHP-DI](http://php-di.org) +- [PimpleInterop](https://github.com/moufmouf/pimple-interop) +- [Ultra-Lite Container](https://github.com/ultra-lite/container) + +### Middlewares implementing `ContainerInterface` + +- [Alias-Container](https://github.com/thecodingmachine/alias-container): add + aliases support to any container +- [Prefixer-Container](https://github.com/thecodingmachine/prefixer-container): + dynamically prefix identifiers +- [Lazy-Container](https://github.com/snapshotpl/lazy-container): lazy services + +### Projects using `ContainerInterface` + +The list below contains only a sample of all the projects consuming `ContainerInterface`. For a more complete list have a look [here](http://packanalyst.com/class?q=Interop%5CContainer%5CContainerInterface). + +| | Downloads | +| --- | --- | +| [Adroit](https://github.com/bitexpert/adroit) | ![](https://img.shields.io/packagist/dt/bitexpert/adroit.svg) | +| [Behat](https://github.com/Behat/Behat/pull/974) | ![](https://img.shields.io/packagist/dt/behat/behat.svg) | +| [blast-facades](https://github.com/phpthinktank/blast-facades): Minimize complexity and represent dependencies as facades. | ![](https://img.shields.io/packagist/dt/blast/facades.svg) | +| [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di): an extension to [Silex](http://silex.sensiolabs.org/) that adds support for any *container-interop* compatible container | ![](https://img.shields.io/packagist/dt/mouf/interop.silex.di.svg) | +| [mindplay/walkway](https://github.com/mindplay-dk/walkway): a modular request router | ![](https://img.shields.io/packagist/dt/mindplay/walkway.svg) | +| [mindplay/middleman](https://github.com/mindplay-dk/middleman): minimalist PSR-7 middleware dispatcher | ![](https://img.shields.io/packagist/dt/mindplay/middleman.svg) | +| [PHP-DI/Invoker](https://github.com/PHP-DI/Invoker): extensible and configurable invoker/dispatcher | ![](https://img.shields.io/packagist/dt/php-di/invoker.svg) | +| [Prophiler](https://github.com/fabfuel/prophiler) | ![](https://img.shields.io/packagist/dt/fabfuel/prophiler.svg) | +| [Silly](https://github.com/mnapoli/silly): CLI micro-framework | ![](https://img.shields.io/packagist/dt/mnapoli/silly.svg) | +| [Slim v3](https://github.com/slimphp/Slim) | ![](https://img.shields.io/packagist/dt/slim/slim.svg) | +| [Splash](http://mouf-php.com/packages/mouf/mvc.splash-common/version/8.0-dev/README.md) | ![](https://img.shields.io/packagist/dt/mouf/mvc.splash-common.svg) | +| [Woohoo Labs. Harmony](https://github.com/woohoolabs/harmony): a flexible micro-framework | ![](https://img.shields.io/packagist/dt/woohoolabs/harmony.svg) | +| [zend-expressive](https://github.com/zendframework/zend-expressive) | ![](https://img.shields.io/packagist/dt/zendframework/zend-expressive.svg) | + + +## Workflow + +Everyone is welcome to join and contribute. + +The general workflow looks like this: + +1. Someone opens a discussion (GitHub issue) to suggest an interface +1. Feedback is gathered +1. The interface is added to a development branch +1. We release alpha versions so that the interface can be experimented with +1. Discussions and edits ensue until the interface is deemed stable by a general consensus +1. A new minor version of the package is released + +We try to not break BC by creating new interfaces instead of editing existing ones. + +While we currently work on interfaces, we are open to anything that might help towards interoperability, may that +be code, best practices, etc. diff --git a/vendor/container-interop/container-interop/composer.json b/vendor/container-interop/container-interop/composer.json new file mode 100644 index 0000000000..855f766723 --- /dev/null +++ b/vendor/container-interop/container-interop/composer.json @@ -0,0 +1,15 @@ +{ + "name": "container-interop/container-interop", + "type": "library", + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "license": "MIT", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "require": { + "psr/container": "^1.0" + } +} diff --git a/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md b/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md new file mode 100644 index 0000000000..59f3d5599f --- /dev/null +++ b/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md @@ -0,0 +1,114 @@ +# ContainerInterface Meta Document + +## Introduction + +This document describes the process and discussions that lead to the `ContainerInterface`. +Its goal is to explain the reasons behind each decision. + +## Goal + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters. + +By standardizing such a behavior, frameworks and libraries using the `ContainerInterface` +could work with any compatible container. +That would allow end users to choose their own container based on their own preferences. + +It is important to distinguish the two usages of a container: + +- configuring entries +- fetching entries + +Most of the time, those two sides are not used by the same party. +While it is often end users who tend to configure entries, it is generally the framework that fetch +entries to build the application. + +This is why this interface focuses only on how entries can be fetched from a container. + +## Interface name + +The interface name has been thoroughly discussed and was decided by a vote. + +The list of options considered with their respective votes are: + +- `ContainerInterface`: +8 +- `ProviderInterface`: +2 +- `LocatorInterface`: 0 +- `ReadableContainerInterface`: -5 +- `ServiceLocatorInterface`: -6 +- `ObjectFactory`: -6 +- `ObjectStore`: -8 +- `ConsumerInterface`: -9 + +[Full results of the vote](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) + +The complete discussion can be read in [the issue #1](https://github.com/container-interop/container-interop/issues/1). + +## Interface methods + +The choice of which methods the interface would contain was made after a statistical analysis of existing containers. +The results of this analysis are available [in this document](https://gist.github.com/mnapoli/6159681). + +The summary of the analysis showed that: + +- all containers offer a method to get an entry by its id +- a large majority name such method `get()` +- for all containers, the `get()` method has 1 mandatory parameter of type string +- some containers have an optional additional argument for `get()`, but it doesn't have the same purpose between containers +- a large majority of the containers offer a method to test if it can return an entry by its id +- a majority name such method `has()` +- for all containers offering `has()`, the method has exactly 1 parameter of type string +- a large majority of the containers throw an exception rather than returning null when an entry is not found in `get()` +- a large majority of the containers don't implement `ArrayAccess` + +The question of whether to include methods to define entries has been discussed in +[issue #1](https://github.com/container-interop/container-interop/issues/1). +It has been judged that such methods do not belong in the interface described here because it is out of its scope +(see the "Goal" section). + +As a result, the `ContainerInterface` contains two methods: + +- `get()`, returning anything, with one mandatory string parameter. Should throw an exception if the entry is not found. +- `has()`, returning a boolean, with one mandatory string parameter. + +### Number of parameters in `get()` method + +While `ContainerInterface` only defines one mandatory parameter in `get()`, it is not incompatible with +existing containers that have additional optional parameters. PHP allows an implementation to offer more parameters +as long as they are optional, because the implementation *does* satisfy the interface. + +This issue has been discussed in [issue #6](https://github.com/container-interop/container-interop/issues/6). + +### Type of the `$id` parameter + +The type of the `$id` parameter in `get()` and `has()` has been discussed in +[issue #6](https://github.com/container-interop/container-interop/issues/6). +While `string` is used in all the containers that were analyzed, it was suggested that allowing +anything (such as objects) could allow containers to offer a more advanced query API. + +An example given was to use the container as an object builder. The `$id` parameter would then be an +object that would describe how to create an instance. + +The conclusion of the discussion was that this was beyond the scope of getting entries from a container without +knowing how the container provided them, and it was more fit for a factory. + +## Contributors + +Are listed here all people that contributed in the discussions or votes, by alphabetical order: + +- [Amy Stephen](https://github.com/AmyStephen) +- [David Négrier](https://github.com/moufmouf) +- [Don Gilbert](https://github.com/dongilbert) +- [Jason Judge](https://github.com/judgej) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Paul M. Jones](https://github.com/pmjones) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) +- [Taylor Otwell](https://github.com/taylorotwell) + +## Relevant links + +- [`ContainerInterface.php`](https://github.com/container-interop/container-interop/blob/master/src/Interop/Container/ContainerInterface.php) +- [List of all issues](https://github.com/container-interop/container-interop/issues?labels=ContainerInterface&milestone=&page=1&state=closed) +- [Vote for the interface name](https://github.com/container-interop/container-interop/wiki/%231-interface-name:-Vote) diff --git a/vendor/container-interop/container-interop/docs/ContainerInterface.md b/vendor/container-interop/container-interop/docs/ContainerInterface.md new file mode 100644 index 0000000000..bda973d6fc --- /dev/null +++ b/vendor/container-interop/container-interop/docs/ContainerInterface.md @@ -0,0 +1,158 @@ +Container interface +=================== + +This document describes a common interface for dependency injection containers. + +The goal set by `ContainerInterface` is to standardize how frameworks and libraries make use of a +container to obtain objects and parameters (called *entries* in the rest of this document). + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the `ContainerInterface` in a dependency injection-related library or framework. +Users of dependency injections containers (DIC) are referred to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Specification +----------------- + +### 1.1 Basics + +- The `Interop\Container\ContainerInterface` exposes two methods : `get` and `has`. + +- `get` takes one mandatory parameter: an entry identifier. It MUST be a string. + A call to `get` can return anything (a *mixed* value), or throws an exception if the identifier + is not known to the container. Two successive calls to `get` with the same + identifier SHOULD return the same value. However, depending on the `implementor` + design and/or `user` configuration, different values might be returned, so + `user` SHOULD NOT rely on getting the same value on 2 successive calls. + While `ContainerInterface` only defines one mandatory parameter in `get()`, implementations + MAY accept additional optional parameters. + +- `has` takes one unique parameter: an entry identifier. It MUST return `true` + if an entry identifier is known to the container and `false` if it is not. + `has($id)` returning true does not mean that `get($id)` will not throw an exception. + It does however mean that `get($id)` will not throw a `NotFoundException`. + +### 1.2 Exceptions + +Exceptions directly thrown by the container MUST implement the +[`Interop\Container\Exception\ContainerException`](../src/Interop/Container/Exception/ContainerException.php). + +A call to the `get` method with a non-existing id SHOULD throw a +[`Interop\Container\Exception\NotFoundException`](../src/Interop/Container/Exception/NotFoundException.php). + +### 1.3 Additional features + +This section describes additional features that MAY be added to a container. Containers are not +required to implement these features to respect the ContainerInterface. + +#### 1.3.1 Delegate lookup feature + +The goal of the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +A container implementing this feature: + +- MUST implement the `ContainerInterface` +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the `ContainerInterface`. + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the `ContainerInterface`). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +2. Package +---------- + +The interfaces and classes described as well as relevant exception are provided as part of the +[container-interop/container-interop](https://packagist.org/packages/container-interop/container-interop) package. + +3. `Interop\Container\ContainerInterface` +----------------------------------------- + +```php +setParentContainer($this); + } + } + ... + } +} + +``` + +**Cons:** + +Cons have been extensively discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-51721777). +Basically, forcing a setter into an interface is a bad idea. Setters are similar to constructor arguments, +and it's a bad idea to standardize a constructor: how the delegate container is configured into a container is an implementation detail. This outweights the benefits of the interface. + +### 4.4 Alternative: no exception case for delegate lookups + +Originally, the proposed wording for delegate lookup calls was: + +> Important! The lookup MUST be performed on the delegate container **only**, not on the container itself. + +This was later replaced by: + +> Important! By default, the lookup SHOULD be performed on the delegate container **only**, not on the container itself. +> +> It is however allowed for containers to provide exception cases for special entries, and a way to lookup +> into the same container (or another container) instead of the delegate container. + +Exception cases have been allowed to avoid breaking dependencies with some services that must be provided +by the container (on @njasm proposal). This was proposed here: https://github.com/container-interop/container-interop/pull/20#issuecomment-56597235 + +### 4.5 Alternative: having one of the containers act as the composite container + +In real-life scenarios, we usually have a big framework (Symfony 2, Zend Framework 2, etc...) and we want to +add another DI container to this container. Most of the time, the "big" framework will be responsible for +creating the controller's instances, using it's own DI container. Until *container-interop* is fully adopted, +the "big" framework will not be aware of the existence of a composite container that it should use instead +of its own container. + +For this real-life use cases, @mnapoli and @moufmouf proposed to extend the "big" framework's DI container +to make it act as a composite container. + +This has been discussed [here](https://github.com/container-interop/container-interop/pull/8#issuecomment-40367194) +and [here](http://mouf-php.com/container-interop-whats-next#solution4). + +This was implemented in Symfony 2 using: + +- [interop.symfony.di](https://github.com/thecodingmachine/interop.symfony.di/tree/v0.1.0) +- [framework interop](https://github.com/mnapoli/framework-interop/) + +This was implemented in Silex using: + +- [interop.silex.di](https://github.com/thecodingmachine/interop.silex.di) + +Having a container act as the composite container is not part of the delegate lookup standard because it is +simply a temporary design pattern used to make existing frameworks that do not support yet ContainerInterop +play nice with other DI containers. + + +5. Implementations +------------------ + +The following projects already implement the delegate lookup feature: + +- [Mouf](http://mouf-php.com), through the [`setDelegateLookupContainer` method](https://github.com/thecodingmachine/mouf/blob/2.0/src/Mouf/MoufManager.php#L2120) +- [PHP-DI](http://php-di.org/), through the [`$wrapperContainer` parameter of the constructor](https://github.com/mnapoli/PHP-DI/blob/master/src/DI/Container.php#L72) +- [pimple-interop](https://github.com/moufmouf/pimple-interop), through the [`$container` parameter of the constructor](https://github.com/moufmouf/pimple-interop/blob/master/src/Interop/Container/Pimple/PimpleInterop.php#L62) + +6. People +--------- + +Are listed here all people that contributed in the discussions, by alphabetical order: + +- [Alexandru Pătrănescu](https://github.com/drealecs) +- [Ben Peachey](https://github.com/potherca) +- [David Négrier](https://github.com/moufmouf) +- [Jeremy Lindblom](https://github.com/jeremeamia) +- [Marco Pivetta](https://github.com/Ocramius) +- [Matthieu Napoli](https://github.com/mnapoli) +- [Nelson J Morais](https://github.com/njasm) +- [Phil Sturgeon](https://github.com/philsturgeon) +- [Stephan Hochdörfer](https://github.com/shochdoerfer) + +7. Relevant Links +----------------- + +_**Note:** Order descending chronologically._ + +- [Pull request on the delegate lookup feature](https://github.com/container-interop/container-interop/pull/20) +- [Pull request on the interface idea](https://github.com/container-interop/container-interop/pull/8) +- [Original article exposing the delegate lookup idea along many others](http://mouf-php.com/container-interop-whats-next) + diff --git a/vendor/container-interop/container-interop/docs/Delegate-lookup.md b/vendor/container-interop/container-interop/docs/Delegate-lookup.md new file mode 100644 index 0000000000..f64a8f785a --- /dev/null +++ b/vendor/container-interop/container-interop/docs/Delegate-lookup.md @@ -0,0 +1,60 @@ +Delegate lookup feature +======================= + +This document describes a standard for dependency injection containers. + +The goal set by the *delegate lookup* feature is to allow several containers to share entries. +Containers implementing this feature can perform dependency lookups in other containers. + +Containers implementing this feature will offer a greater lever of interoperability +with other containers. Implementation of this feature is therefore RECOMMENDED. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The word `implementor` in this document is to be interpreted as someone +implementing the delegate lookup feature in a dependency injection-related library or framework. +Users of dependency injections containers (DIC) are referred to as `user`. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +1. Vocabulary +------------- + +In a dependency injection container, the container is used to fetch entries. +Entries can have dependencies on other entries. Usually, these other entries are fetched by the container. + +The *delegate lookup* feature is the ability for a container to fetch dependencies in +another container. In the rest of the document, the word "container" will reference the container +implemented by the implementor. The word "delegate container" will reference the container we are +fetching the dependencies from. + +2. Specification +---------------- + +A container implementing the *delegate lookup* feature: + +- MUST implement the [`ContainerInterface`](ContainerInterface.md) +- MUST provide a way to register a delegate container (using a constructor parameter, or a setter, + or any possible way). The delegate container MUST implement the [`ContainerInterface`](ContainerInterface.md). + +When a container is configured to use a delegate container for dependencies: + +- Calls to the `get` method should only return an entry if the entry is part of the container. + If the entry is not part of the container, an exception should be thrown + (as requested by the [`ContainerInterface`](ContainerInterface.md)). +- Calls to the `has` method should only return `true` if the entry is part of the container. + If the entry is not part of the container, `false` should be returned. +- If the fetched entry has dependencies, **instead** of performing + the dependency lookup in the container, the lookup is performed on the *delegate container*. + +Important: By default, the dependency lookups SHOULD be performed on the delegate container **only**, not on the container itself. + +It is however allowed for containers to provide exception cases for special entries, and a way to lookup +into the same container (or another container) instead of the delegate container. + +3. Package / Interface +---------------------- + +This feature is not tied to any code, interface or package. diff --git a/vendor/container-interop/container-interop/docs/images/interoperating_containers.png b/vendor/container-interop/container-interop/docs/images/interoperating_containers.png new file mode 100644 index 0000000000..1d3fdd0ddb Binary files /dev/null and b/vendor/container-interop/container-interop/docs/images/interoperating_containers.png differ diff --git a/vendor/container-interop/container-interop/docs/images/priority.png b/vendor/container-interop/container-interop/docs/images/priority.png new file mode 100644 index 0000000000..d02cb7d1f1 Binary files /dev/null and b/vendor/container-interop/container-interop/docs/images/priority.png differ diff --git a/vendor/container-interop/container-interop/docs/images/side_by_side_containers.png b/vendor/container-interop/container-interop/docs/images/side_by_side_containers.png new file mode 100644 index 0000000000..87884bc292 Binary files /dev/null and b/vendor/container-interop/container-interop/docs/images/side_by_side_containers.png differ diff --git a/vendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.php b/vendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.php new file mode 100644 index 0000000000..a75468f6a4 --- /dev/null +++ b/vendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.php @@ -0,0 +1,15 @@ +2.2,<2.4" + }, + "autoload": { + "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" } + }, + "autoload-dev": { + "psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php new file mode 100644 index 0000000000..0d2cb58587 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php @@ -0,0 +1,118 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * APC cache provider. + * + * @link www.doctrine-project.org + * @deprecated since version 1.6, use ApcuCache instead + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ApcCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return apc_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return apc_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return apc_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // apc_delete returns false if the id does not exist + return apc_delete($id) || ! apc_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return apc_clear_cache() && apc_clear_cache('user'); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return apc_fetch($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $result = apc_store($keysAndValues, null, $lifetime); + + return empty($result); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = apc_cache_info('', true); + $sma = apc_sma_info(); + + // @TODO - Temporary fix @see https://github.com/krakjoe/apcu/pull/42 + if (PHP_VERSION_ID >= 50500) { + $info['num_hits'] = isset($info['num_hits']) ? $info['num_hits'] : $info['nhits']; + $info['num_misses'] = isset($info['num_misses']) ? $info['num_misses'] : $info['nmisses']; + $info['start_time'] = isset($info['start_time']) ? $info['start_time'] : $info['stime']; + } + + return [ + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php new file mode 100644 index 0000000000..5449b02e75 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcuCache.php @@ -0,0 +1,116 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * APCu cache provider. + * + * @link www.doctrine-project.org + * @since 1.6 + * @author Kévin Dunglas + */ +class ApcuCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return apcu_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return apcu_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // apcu_delete returns false if the id does not exist + return apcu_delete($id) || ! apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + $result = apcu_delete($keys); + + return false !== $result && count($result) !== count($keys); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return apcu_fetch($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $result = apcu_store($keysAndValues, null, $lifetime); + + return empty($result); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = apcu_cache_info(true); + $sma = apcu_sma_info(); + + return [ + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php new file mode 100644 index 0000000000..6610cc2173 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php @@ -0,0 +1,142 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Array cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ArrayCache extends CacheProvider +{ + /** + * @var array[] $data each element being a tuple of [$data, $expiration], where the expiration is int|bool + */ + private $data = []; + + /** + * @var int + */ + private $hitsCount = 0; + + /** + * @var int + */ + private $missesCount = 0; + + /** + * @var int + */ + private $upTime; + + /** + * {@inheritdoc} + */ + public function __construct() + { + $this->upTime = time(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + if (! $this->doContains($id)) { + $this->missesCount += 1; + + return false; + } + + $this->hitsCount += 1; + + return $this->data[$id][0]; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + if (! isset($this->data[$id])) { + return false; + } + + $expiration = $this->data[$id][1]; + + if ($expiration && $expiration < time()) { + $this->doDelete($id); + + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $this->data[$id] = [$data, $lifeTime ? time() + $lifeTime : false]; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + unset($this->data[$id]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->data = []; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return [ + Cache::STATS_HITS => $this->hitsCount, + Cache::STATS_MISSES => $this->missesCount, + Cache::STATS_UPTIME => $this->upTime, + Cache::STATS_MEMORY_USAGE => null, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php new file mode 100644 index 0000000000..89fe32307f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php @@ -0,0 +1,116 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + * @author Kévin Dunglas + */ +interface Cache +{ + const STATS_HITS = 'hits'; + const STATS_MISSES = 'misses'; + const STATS_UPTIME = 'uptime'; + const STATS_MEMORY_USAGE = 'memory_usage'; + const STATS_MEMORY_AVAILABLE = 'memory_available'; + /** + * Only for backward compatibility (may be removed in next major release) + * + * @deprecated + */ + const STATS_MEMORY_AVAILIABLE = 'memory_available'; + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php new file mode 100644 index 0000000000..546c0ec1dd --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php @@ -0,0 +1,341 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base class for cache provider implementations. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + * @author Benoit Burnichon + */ +abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiOperationCache +{ + const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; + + /** + * The namespace to prefix all cache ids with. + * + * @var string + */ + private $namespace = ''; + + /** + * The namespace version. + * + * @var integer|null + */ + private $namespaceVersion; + + /** + * Sets the namespace to prefix all cache ids with. + * + * @param string $namespace + * + * @return void + */ + public function setNamespace($namespace) + { + $this->namespace = (string) $namespace; + $this->namespaceVersion = null; + } + + /** + * Retrieves the namespace that prefixes all cache ids. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * {@inheritdoc} + */ + public function fetch($id) + { + return $this->doFetch($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function fetchMultiple(array $keys) + { + if (empty($keys)) { + return []; + } + + // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys + $namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys)); + $items = $this->doFetchMultiple($namespacedKeys); + $foundItems = []; + + // no internal array function supports this sort of mapping: needs to be iterative + // this filters and combines keys in one pass + foreach ($namespacedKeys as $requestedKey => $namespacedKey) { + if (isset($items[$namespacedKey]) || array_key_exists($namespacedKey, $items)) { + $foundItems[$requestedKey] = $items[$namespacedKey]; + } + } + + return $foundItems; + } + + /** + * {@inheritdoc} + */ + public function saveMultiple(array $keysAndValues, $lifetime = 0) + { + $namespacedKeysAndValues = []; + foreach ($keysAndValues as $key => $value) { + $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value; + } + + return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function contains($id) + { + return $this->doContains($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function save($id, $data, $lifeTime = 0) + { + return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $keys) + { + return $this->doDeleteMultiple(array_map(array($this, 'getNamespacedId'), $keys)); + } + + /** + * {@inheritdoc} + */ + public function delete($id) + { + return $this->doDelete($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function getStats() + { + return $this->doGetStats(); + } + + /** + * {@inheritDoc} + */ + public function flushAll() + { + return $this->doFlush(); + } + + /** + * {@inheritDoc} + */ + public function deleteAll() + { + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->getNamespaceVersion() + 1; + + if ($this->doSave($namespaceCacheKey, $namespaceVersion)) { + $this->namespaceVersion = $namespaceVersion; + + return true; + } + + return false; + } + + /** + * Prefixes the passed id with the configured namespace value. + * + * @param string $id The id to namespace. + * + * @return string The namespaced id. + */ + private function getNamespacedId(string $id) : string + { + $namespaceVersion = $this->getNamespaceVersion(); + + return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); + } + + /** + * Returns the namespace cache key. + * + * @return string + */ + private function getNamespaceCacheKey() : string + { + return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); + } + + /** + * Returns the namespace version. + * + * @return integer + */ + private function getNamespaceVersion() : int + { + if (null !== $this->namespaceVersion) { + return $this->namespaceVersion; + } + + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1; + + return $this->namespaceVersion; + } + + /** + * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it. + * + * @param array $keys Array of keys to retrieve from cache + * @return array Array of values retrieved for the given keys. + */ + protected function doFetchMultiple(array $keys) + { + $returnValues = []; + + foreach ($keys as $key) { + if (false !== ($item = $this->doFetch($key)) || $this->doContains($key)) { + $returnValues[$key] = $item; + } + } + + return $returnValues; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id. + */ + abstract protected function doFetch($id); + + /** + * 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. + */ + abstract protected function doContains($id); + + /** + * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it. + * + * @param array $keysAndValues Array of keys and values to save in cache + * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these + * cache entries (0 => infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $success = true; + + foreach ($keysAndValues as $key => $value) { + if (!$this->doSave($key, $value, $lifetime)) { + $success = false; + } + } + + return $success; + } + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + abstract protected function doSave($id, $data, $lifeTime = 0); + + /** + * Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it. + * + * @param array $keys Array of keys to delete from cache + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't + */ + protected function doDeleteMultiple(array $keys) + { + $success = true; + + foreach ($keys as $key) { + if (! $this->doDelete($key)) { + $success = false; + } + } + + return $success; + } + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doDelete($id); + + /** + * Flushes all cache entries. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + abstract protected function doFlush(); + + /** + * Retrieves cached information from the data store. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + abstract protected function doGetStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php new file mode 100644 index 0000000000..32534aa4e3 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php @@ -0,0 +1,205 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Cache provider that allows to easily chain multiple cache providers + * + * @author Michaël Gallego + */ +class ChainCache extends CacheProvider +{ + /** + * @var CacheProvider[] + */ + private $cacheProviders = []; + + /** + * Constructor + * + * @param CacheProvider[] $cacheProviders + */ + public function __construct($cacheProviders = []) + { + $this->cacheProviders = $cacheProviders instanceof \Traversable + ? iterator_to_array($cacheProviders, false) + : array_values($cacheProviders); + } + + /** + * {@inheritDoc} + */ + public function setNamespace($namespace) + { + parent::setNamespace($namespace); + + foreach ($this->cacheProviders as $cacheProvider) { + $cacheProvider->setNamespace($namespace); + } + } + + /** + * {@inheritDoc} + */ + protected function doFetch($id) + { + foreach ($this->cacheProviders as $key => $cacheProvider) { + if ($cacheProvider->doContains($id)) { + $value = $cacheProvider->doFetch($id); + + // We populate all the previous cache layers (that are assumed to be faster) + for ($subKey = $key - 1 ; $subKey >= 0 ; $subKey--) { + $this->cacheProviders[$subKey]->doSave($id, $value); + } + + return $value; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + /* @var $traversedProviders CacheProvider[] */ + $traversedProviders = []; + $keysCount = count($keys); + $fetchedValues = []; + + foreach ($this->cacheProviders as $key => $cacheProvider) { + $fetchedValues = $cacheProvider->doFetchMultiple($keys); + + // We populate all the previous cache layers (that are assumed to be faster) + if (count($fetchedValues) === $keysCount) { + foreach ($traversedProviders as $previousCacheProvider) { + $previousCacheProvider->doSaveMultiple($fetchedValues); + } + + return $fetchedValues; + } + + $traversedProviders[] = $cacheProvider; + } + + return $fetchedValues; + } + + /** + * {@inheritDoc} + */ + protected function doContains($id) + { + foreach ($this->cacheProviders as $cacheProvider) { + if ($cacheProvider->doContains($id)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $stored = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $stored = $cacheProvider->doSave($id, $data, $lifeTime) && $stored; + } + + return $stored; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $stored = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $stored = $cacheProvider->doSaveMultiple($keysAndValues, $lifetime) && $stored; + } + + return $stored; + } + + /** + * {@inheritDoc} + */ + protected function doDelete($id) + { + $deleted = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $deleted = $cacheProvider->doDelete($id) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + $deleted = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $deleted = $cacheProvider->doDeleteMultiple($keys) && $deleted; + } + + return $deleted; + } + + /** + * {@inheritDoc} + */ + protected function doFlush() + { + $flushed = true; + + foreach ($this->cacheProviders as $cacheProvider) { + $flushed = $cacheProvider->doFlush() && $flushed; + } + + return $flushed; + } + + /** + * {@inheritDoc} + */ + protected function doGetStats() + { + // We return all the stats from all adapters + $stats = []; + + foreach ($this->cacheProviders as $cacheProvider) { + $stats[] = $cacheProvider->doGetStats(); + } + + return $stats; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php new file mode 100644 index 0000000000..3a91eaf3a2 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache that can be flushed. + * + * Intended to be used for partial clearing of a cache namespace. For a more + * global "flushing", see {@see FlushableCache}. + * + * @link www.doctrine-project.org + * @since 1.4 + * @author Adirelle + */ +interface ClearableCache +{ + /** + * Deletes all cache entries in the current cache namespace. + * + * @return bool TRUE if the cache entries were successfully deleted, FALSE otherwise. + */ + public function deleteAll(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php new file mode 100644 index 0000000000..fe35533a8c --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Couchbase; + +/** + * Couchbase cache provider. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Michael Nitschinger + */ +class CouchbaseCache extends CacheProvider +{ + /** + * @var Couchbase|null + */ + private $couchbase; + + /** + * Sets the Couchbase instance to use. + * + * @param Couchbase $couchbase + * + * @return void + */ + public function setCouchbase(Couchbase $couchbase) + { + $this->couchbase = $couchbase; + } + + /** + * Gets the Couchbase instance used by the cache. + * + * @return Couchbase|null + */ + public function getCouchbase() + { + return $this->couchbase; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->couchbase->get($id) ?: false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (null !== $this->couchbase->get($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->couchbase->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->couchbase->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->couchbase->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->couchbase->getStats(); + $servers = $this->couchbase->getServers(); + $server = explode(":", $servers[0]); + $key = $server[0] . ":" . "11210"; + $stats = $stats[$key]; + return [ + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php new file mode 100644 index 0000000000..b07cf6d4e4 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ExtMongoDBCache.php @@ -0,0 +1,221 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use MongoDB\BSON\Binary; +use MongoDB\BSON\UTCDateTime; +use MongoDB\Collection; +use MongoDB\Database; +use MongoDB\Driver\Exception\Exception; +use MongoDB\Model\BSONDocument; + +/** + * MongoDB cache provider for ext-mongodb + * + * @internal Do not use - will be removed in 2.0. Use MongoDBCache instead + */ +class ExtMongoDBCache extends CacheProvider +{ + /** + * @var Database + */ + private $database; + + /** + * @var Collection + */ + private $collection; + + /** + * @var bool + */ + private $expirationIndexCreated = false; + + /** + * Constructor. + * + * This provider will default to the write concern and read preference + * options set on the Database instance (or inherited from MongoDB or + * Client). Using an unacknowledged write concern (< 1) may make the return + * values of delete() and save() unreliable. Reading from secondaries may + * make contain() and fetch() unreliable. + * + * @see http://www.php.net/manual/en/mongo.readpreferences.php + * @see http://www.php.net/manual/en/mongo.writeconcerns.php + * @param Collection $collection + */ + public function __construct(Collection $collection) + { + // Ensure there is no typemap set - we want to use our own + $this->collection = $collection->withOptions(['typeMap' => null]); + $this->database = new Database($collection->getManager(), $collection->getDatabaseName()); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + return false; + } + + return unserialize($document[MongoDBCache::DATA_FIELD]->getData()); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $this->collection->updateOne( + ['_id' => $id], + ['$set' => [ + MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new UTCDateTime((time() + $lifeTime) * 1000): null), + MongoDBCache::DATA_FIELD => new Binary(serialize($data), Binary::TYPE_GENERIC), + ]], + ['upsert' => true] + ); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + try { + $this->collection->deleteOne(['_id' => $id]); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + try { + // Use remove() in lieu of drop() to maintain any collection indexes + $this->collection->deleteMany([]); + } catch (Exception $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $uptime = null; + $memoryUsage = null; + + try { + $serverStatus = $this->database->command([ + 'serverStatus' => 1, + 'locks' => 0, + 'metrics' => 0, + 'recordStats' => 0, + 'repl' => 0, + ])->toArray()[0]; + $uptime = $serverStatus['uptime'] ?? null; + } catch (Exception $e) { + } + + try { + $collStats = $this->database->command(['collStats' => $this->collection->getCollectionName()])->toArray()[0]; + $memoryUsage = $collStats['size'] ?? null; + } catch (Exception $e) { + } + + return [ + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => $uptime, + Cache::STATS_MEMORY_USAGE => $memoryUsage, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } + + /** + * Check if the document is expired. + * + * @param BSONDocument $document + * + * @return bool + */ + private function isExpired(BSONDocument $document): bool + { + return isset($document[MongoDBCache::EXPIRATION_FIELD]) && + $document[MongoDBCache::EXPIRATION_FIELD] instanceof UTCDateTime && + $document[MongoDBCache::EXPIRATION_FIELD]->toDateTime() < new \DateTime(); + } + + private function createExpirationIndex(): void + { + if ($this->expirationIndexCreated) { + return; + } + + $this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php new file mode 100644 index 0000000000..5293b8fdda --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php @@ -0,0 +1,287 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + * @author Tobias Schultze + */ +abstract class FileCache extends CacheProvider +{ + /** + * The cache directory. + * + * @var string + */ + protected $directory; + + /** + * The cache file extension. + * + * @var string + */ + private $extension; + + /** + * @var int + */ + private $umask; + + /** + * @var int + */ + private $directoryStringLength; + + /** + * @var int + */ + private $extensionStringLength; + + /** + * @var bool + */ + private $isRunningOnWindows; + + /** + * Constructor. + * + * @param string $directory The cache directory. + * @param string $extension The cache file extension. + * + * @throws \InvalidArgumentException + */ + public function __construct($directory, $extension = '', $umask = 0002) + { + // YES, this needs to be *before* createPathIfNeeded() + if ( ! is_int($umask)) { + throw new \InvalidArgumentException(sprintf( + 'The umask parameter is required to be integer, was: %s', + gettype($umask) + )); + } + $this->umask = $umask; + + if ( ! $this->createPathIfNeeded($directory)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $directory + )); + } + + if ( ! is_writable($directory)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" is not writable.', + $directory + )); + } + + // YES, this needs to be *after* createPathIfNeeded() + $this->directory = realpath($directory); + $this->extension = (string) $extension; + + $this->directoryStringLength = strlen($this->directory); + $this->extensionStringLength = strlen($this->extension); + $this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD'); + } + + /** + * Gets the cache directory. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Gets the cache file extension. + * + * @return string + */ + public function getExtension() + { + return $this->extension; + } + + /** + * @param string $id + * + * @return string + */ + protected function getFilename($id) + { + $hash = hash('sha256', $id); + + // This ensures that the filename is unique and that there are no invalid chars in it. + if ( + '' === $id + || ((strlen($id) * 2 + $this->extensionStringLength) > 255) + || ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258) + ) { + // Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited + // to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API. + // And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259. + // So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents + // collisions between the hash and bin2hex. + $filename = '_' . $hash; + } else { + $filename = bin2hex($id); + } + + return $this->directory + . DIRECTORY_SEPARATOR + . substr($hash, 0, 2) + . DIRECTORY_SEPARATOR + . $filename + . $this->extension; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $filename = $this->getFilename($id); + + return @unlink($filename) || ! file_exists($filename); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + foreach ($this->getIterator() as $name => $file) { + if ($file->isDir()) { + // Remove the intermediate directories which have been created to balance the tree. It only takes effect + // if the directory is empty. If several caches share the same directory but with different file extensions, + // the other ones are not removed. + @rmdir($name); + } elseif ($this->isFilenameEndingWithExtension($name)) { + // If an extension is set, only remove files which end with the given extension. + // If no extension is set, we have no other choice than removing everything. + @unlink($name); + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $usage = 0; + foreach ($this->getIterator() as $name => $file) { + if (! $file->isDir() && $this->isFilenameEndingWithExtension($name)) { + $usage += $file->getSize(); + } + } + + $free = disk_free_space($this->directory); + + return [ + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $usage, + Cache::STATS_MEMORY_AVAILABLE => $free, + ]; + } + + /** + * Create path if needed. + * + * @param string $path + * @return bool TRUE on success or if path already exists, FALSE if path cannot be created. + */ + private function createPathIfNeeded(string $path) : bool + { + if ( ! is_dir($path)) { + if (false === @mkdir($path, 0777 & (~$this->umask), true) && !is_dir($path)) { + return false; + } + } + + return true; + } + + /** + * Writes a string content to file in an atomic way. + * + * @param string $filename Path to the file where to write the data. + * @param string $content The content to write + * + * @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error. + */ + protected function writeFile(string $filename, string $content) : bool + { + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if ( ! $this->createPathIfNeeded($filepath)) { + return false; + } + + if ( ! is_writable($filepath)) { + return false; + } + + $tmpFile = tempnam($filepath, 'swap'); + @chmod($tmpFile, 0666 & (~$this->umask)); + + if (file_put_contents($tmpFile, $content) !== false) { + @chmod($tmpFile, 0666 & (~$this->umask)); + if (@rename($tmpFile, $filename)) { + return true; + } + + @unlink($tmpFile); + } + + return false; + } + + /** + * @return \Iterator + */ + private function getIterator() : \Iterator + { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + } + + /** + * @param string $name The filename + * + * @return bool + */ + private function isFilenameEndingWithExtension(string $name) : bool + { + return '' === $this->extension + || strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php new file mode 100644 index 0000000000..d988294f6f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Filesystem cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class FilesystemCache extends FileCache +{ + const EXTENSION = '.doctrinecache.data'; + + /** + * {@inheritdoc} + */ + public function __construct($directory, $extension = self::EXTENSION, $umask = 0002) + { + parent::__construct($directory, $extension, $umask); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $data = ''; + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (int) $line; + } + + if ($lifetime !== 0 && $lifetime < time()) { + fclose($resource); + + return false; + } + + while (false !== ($line = fgets($resource))) { + $data .= $line; + } + + fclose($resource); + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (int) $line; + } + + fclose($resource); + + return $lifetime === 0 || $lifetime > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $data = serialize($data); + $filename = $this->getFilename($id); + + return $this->writeFile($filename, $lifeTime . PHP_EOL . $data); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php new file mode 100644 index 0000000000..4311d4f593 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache that can be flushed. + * + * @link www.doctrine-project.org + * @since 1.4 + * @author Adirelle + */ +interface FlushableCache +{ + /** + * Flushes all cache entries, globally. + * + * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + public function flushAll(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/LegacyMongoDBCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/LegacyMongoDBCache.php new file mode 100644 index 0000000000..91a78bffbc --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/LegacyMongoDBCache.php @@ -0,0 +1,194 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use MongoBinData; +use MongoCollection; +use MongoCursorException; +use MongoDate; + +/** + * MongoDB cache provider. + * + * @author Jeremy Mikola + * @internal Do not use - will be removed in 2.0. Use MongoDBCache instead + */ +class LegacyMongoDBCache extends CacheProvider +{ + /** + * @var MongoCollection + */ + private $collection; + + /** + * @var bool + */ + private $expirationIndexCreated = false; + + /** + * Constructor. + * + * This provider will default to the write concern and read preference + * options set on the MongoCollection instance (or inherited from MongoDB or + * MongoClient). Using an unacknowledged write concern (< 1) may make the + * return values of delete() and save() unreliable. Reading from secondaries + * may make contain() and fetch() unreliable. + * + * @see http://www.php.net/manual/en/mongo.readpreferences.php + * @see http://www.php.net/manual/en/mongo.writeconcerns.php + * @param MongoCollection $collection + */ + public function __construct(MongoCollection $collection) + { + @trigger_error('Using the legacy MongoDB cache provider is deprecated and will be removed in 2.0', E_USER_DEPRECATED); + $this->collection = $collection; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + return false; + } + + return unserialize($document[MongoDBCache::DATA_FIELD]->bin); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]); + + if ($document === null) { + return false; + } + + if ($this->isExpired($document)) { + $this->createExpirationIndex(); + $this->doDelete($id); + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $result = $this->collection->update( + ['_id' => $id], + ['$set' => [ + MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null), + MongoDBCache::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY), + ]], + ['upsert' => true, 'multiple' => false] + ); + } catch (MongoCursorException $e) { + return false; + } + + return ($result['ok'] ?? 1) == 1; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + $result = $this->collection->remove(['_id' => $id]); + + return ($result['ok'] ?? 1) == 1; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + // Use remove() in lieu of drop() to maintain any collection indexes + $result = $this->collection->remove(); + + return ($result['ok'] ?? 1) == 1; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $serverStatus = $this->collection->db->command([ + 'serverStatus' => 1, + 'locks' => 0, + 'metrics' => 0, + 'recordStats' => 0, + 'repl' => 0, + ]); + + $collStats = $this->collection->db->command(['collStats' => 1]); + + return [ + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => $serverStatus['uptime'] ?? null, + Cache::STATS_MEMORY_USAGE => $collStats['size'] ?? null, + Cache::STATS_MEMORY_AVAILABLE => null, + ]; + } + + /** + * Check if the document is expired. + * + * @param array $document + * + * @return bool + */ + private function isExpired(array $document) : bool + { + return isset($document[MongoDBCache::EXPIRATION_FIELD]) && + $document[MongoDBCache::EXPIRATION_FIELD] instanceof MongoDate && + $document[MongoDBCache::EXPIRATION_FIELD]->sec < time(); + } + + + private function createExpirationIndex(): void + { + if ($this->expirationIndexCreated) { + return; + } + + $this->expirationIndexCreated = true; + $this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php new file mode 100644 index 0000000000..97ab90a983 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php @@ -0,0 +1,126 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcache; + +/** + * Memcache cache provider. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcacheCache extends CacheProvider +{ + /** + * @var Memcache|null + */ + private $memcache; + + /** + * Sets the memcache instance to use. + * + * @param Memcache $memcache + * + * @return void + */ + public function setMemcache(Memcache $memcache) + { + $this->memcache = $memcache; + } + + /** + * Gets the memcache instance used by the cache. + * + * @return Memcache|null + */ + public function getMemcache() + { + return $this->memcache; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $flags = null; + $this->memcache->get($id, $flags); + + //if memcache has changed the value of "flags", it means the value exists + return ($flags !== null); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcache->set($id, $data, 0, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + // Memcache::delete() returns false if entry does not exist + return $this->memcache->delete($id) || ! $this->doContains($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcache->getStats(); + return [ + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php new file mode 100644 index 0000000000..7bb32a03a1 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php @@ -0,0 +1,156 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcached; + +/** + * Memcached cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcachedCache extends CacheProvider +{ + /** + * @var Memcached|null + */ + private $memcached; + + /** + * Sets the memcache instance to use. + * + * @param Memcached $memcached + * + * @return void + */ + public function setMemcached(Memcached $memcached) + { + $this->memcached = $memcached; + } + + /** + * Gets the memcached instance used by the cache. + * + * @return Memcached|null + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcached->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return $this->memcached->getMulti($keys) ?: []; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime > 30 * 24 * 3600) { + $lifetime = time() + $lifetime; + } + + return $this->memcached->setMulti($keysAndValues, $lifetime); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $this->memcached->get($id); + + return $this->memcached->getResultCode() === Memcached::RES_SUCCESS; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcached->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + return $this->memcached->deleteMulti($keys) + || $this->memcached->getResultCode() === Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcached->delete($id) + || $this->memcached->getResultCode() === Memcached::RES_NOTFOUND; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcached->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcached->getStats(); + $servers = $this->memcached->getServerList(); + $key = $servers[0]['host'] . ':' . $servers[0]['port']; + $stats = $stats[$key]; + return [ + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php new file mode 100644 index 0000000000..238fde41c5 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php @@ -0,0 +1,132 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use MongoCollection; +use MongoDB\Collection; + +/** + * MongoDB cache provider. + * + * @since 1.1 + * @author Jeremy Mikola + */ +class MongoDBCache extends CacheProvider +{ + /** + * The data field will store the serialized PHP value. + */ + const DATA_FIELD = 'd'; + + /** + * The expiration field will store a MongoDate value indicating when the + * cache entry should expire. + * + * With MongoDB 2.2+, entries can be automatically deleted by MongoDB by + * indexing this field with the "expireAfterSeconds" option equal to zero. + * This will direct MongoDB to regularly query for and delete any entries + * whose date is older than the current time. Entries without a date value + * in this field will be ignored. + * + * The cache provider will also check dates on its own, in case expired + * entries are fetched before MongoDB's TTLMonitor pass can expire them. + * + * @see http://docs.mongodb.org/manual/tutorial/expire-data/ + */ + const EXPIRATION_FIELD = 'e'; + + /** + * @var CacheProvider + */ + private $provider; + + /** + * Constructor. + * + * This provider will default to the write concern and read preference + * options set on the collection instance (or inherited from MongoDB or + * MongoClient). Using an unacknowledged write concern (< 1) may make the + * return values of delete() and save() unreliable. Reading from secondaries + * may make contain() and fetch() unreliable. + * + * @see http://www.php.net/manual/en/mongo.readpreferences.php + * @see http://www.php.net/manual/en/mongo.writeconcerns.php + * @param MongoCollection|Collection $collection + */ + public function __construct($collection) + { + if ($collection instanceof MongoCollection) { + @trigger_error('Using a MongoCollection instance for creating a cache adapter is deprecated and will be removed in 2.0', E_USER_DEPRECATED); + $this->provider = new LegacyMongoDBCache($collection); + } elseif ($collection instanceof Collection) { + $this->provider = new ExtMongoDBCache($collection); + } else { + throw new \InvalidArgumentException('Invalid collection given - expected a MongoCollection or MongoDB\Collection instance'); + } + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->provider->doFetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->provider->doContains($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return $this->provider->doSave($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->provider->doDelete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->provider->doFlush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return $this->provider->doGetStats(); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php new file mode 100644 index 0000000000..0abaea18be --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiDeleteCache.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers that allows to put many items at once. + * + * @link www.doctrine-project.org + * @since 1.7 + * @author Benoit Burnichon + * + * @deprecated + */ +interface MultiDeleteCache +{ + /** + * Deletes several cache entries. + * + * @param string[] $keys Array of keys to delete from cache + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + function deleteMultiple(array $keys); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php new file mode 100644 index 0000000000..0d437d143b --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers that allows to get many items at once. + * + * @link www.doctrine-project.org + * @since 1.4 + * @author Asmir Mustafic + * + * @deprecated + */ +interface MultiGetCache +{ + /** + * Returns an associative array of values for keys is found in cache. + * + * @param string[] $keys Array of keys to retrieve from cache + * @return mixed[] Array of retrieved values, indexed by the specified keys. + * Values that couldn't be retrieved are not contained in this array. + */ + function fetchMultiple(array $keys); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiOperationCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiOperationCache.php new file mode 100644 index 0000000000..7d5c0b9fc1 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiOperationCache.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers that supports multiple items manipulation. + * + * @link www.doctrine-project.org + * @since 1.7 + * @author Luís Cobucci + */ +interface MultiOperationCache extends MultiGetCache, MultiDeleteCache, MultiPutCache +{ +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php new file mode 100644 index 0000000000..cb20bdc619 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiPutCache.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers that allows to put many items at once. + * + * @link www.doctrine-project.org + * @since 1.6 + * @author Daniel Gorgan + * + * @deprecated + */ +interface MultiPutCache +{ + /** + * Returns a boolean value indicating if the operation succeeded. + * + * @param array $keysAndValues Array of keys and values to save in cache + * @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these + * cache entries (0 => infinite lifeTime). + * + * @return bool TRUE if the operation was successful, FALSE if it wasn't. + */ + function saveMultiple(array $keysAndValues, $lifetime = 0); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php new file mode 100644 index 0000000000..d243ab6c74 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php @@ -0,0 +1,131 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Php file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class PhpFileCache extends FileCache +{ + const EXTENSION = '.doctrinecache.php'; + + /** + * @var callable + * + * This is cached in a local static variable to avoid instantiating a closure each time we need an empty handler + */ + private static $emptyErrorHandler; + + /** + * {@inheritdoc} + */ + public function __construct($directory, $extension = self::EXTENSION, $umask = 0002) + { + parent::__construct($directory, $extension, $umask); + + self::$emptyErrorHandler = function () { + }; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $value = $this->includeFileForId($id); + + if ($value === null) { + return false; + } + + if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) { + return false; + } + + return $value['data']; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $value = $this->includeFileForId($id); + + if ($value === null) { + return false; + } + + return $value['lifetime'] === 0 || $value['lifetime'] > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $filename = $this->getFilename($id); + + $value = [ + 'lifetime' => $lifeTime, + 'data' => $data + ]; + + if (is_object($data) && method_exists($data, '__set_state')) { + $value = var_export($value, true); + $code = sprintf('writeFile($filename, $code); + } + + /** + * @param string $id + * + * @return array|null + */ + private function includeFileForId(string $id) : ?array + { + $fileName = $this->getFilename($id); + + // note: error suppression is still faster than `file_exists`, `is_file` and `is_readable` + set_error_handler(self::$emptyErrorHandler); + + $value = include $fileName; + + restore_error_handler(); + + if (! isset($value['lifetime'])) { + return null; + } + + return $value; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php new file mode 100644 index 0000000000..a0cb86f976 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php @@ -0,0 +1,161 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Predis\ClientInterface; + +/** + * Predis cache provider. + * + * @author othillo + */ +class PredisCache extends CacheProvider +{ + /** + * @var ClientInterface + */ + private $client; + + /** + * @param ClientInterface $client + * + * @return void + */ + public function __construct(ClientInterface $client) + { + $this->client = $client; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $result = $this->client->get($id); + if (null === $result) { + return false; + } + + return unserialize($result); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + $fetchedItems = call_user_func_array([$this->client, 'mget'], $keys); + + return array_map('unserialize', array_filter(array_combine($keys, $fetchedItems))); + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime) { + $success = true; + + // Keys have lifetime, use SETEX for each of them + foreach ($keysAndValues as $key => $value) { + $response = $this->client->setex($key, $lifetime, serialize($value)); + + if ((string) $response != 'OK') { + $success = false; + } + } + + return $success; + } + + // No lifetime, use MSET + $response = $this->client->mset(array_map(function ($value) { + return serialize($value); + }, $keysAndValues)); + + return (string) $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (bool) $this->client->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $data = serialize($data); + if ($lifeTime > 0) { + $response = $this->client->setex($id, $lifeTime, $data); + } else { + $response = $this->client->set($id, $data); + } + + return $response === true || $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->client->del($id) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + return $this->client->del($keys) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $response = $this->client->flushdb(); + + return $response === true || $response == 'OK'; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->client->info(); + + return [ + Cache::STATS_HITS => $info['Stats']['keyspace_hits'], + Cache::STATS_MISSES => $info['Stats']['keyspace_misses'], + Cache::STATS_UPTIME => $info['Server']['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['Memory']['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php new file mode 100644 index 0000000000..3ea51d507a --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php @@ -0,0 +1,184 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Redis; + +/** + * Redis cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Osman Ungur + */ +class RedisCache extends CacheProvider +{ + /** + * @var Redis|null + */ + private $redis; + + /** + * Sets the redis instance to use. + * + * @param Redis $redis + * + * @return void + */ + public function setRedis(Redis $redis) + { + $redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue()); + $this->redis = $redis; + } + + /** + * Gets the redis instance used by the cache. + * + * @return Redis|null + */ + public function getRedis() + { + return $this->redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->redis->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + $fetchedItems = array_combine($keys, $this->redis->mget($keys)); + + // Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data. + $foundItems = []; + + foreach ($fetchedItems as $key => $value) { + if (false !== $value || $this->redis->exists($key)) { + $foundItems[$key] = $value; + } + } + + return $foundItems; + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + if ($lifetime) { + $success = true; + + // Keys have lifetime, use SETEX for each of them + foreach ($keysAndValues as $key => $value) { + if (!$this->redis->setex($key, $lifetime, $value)) { + $success = false; + } + } + + return $success; + } + + // No lifetime, use MSET + return (bool) $this->redis->mset($keysAndValues); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->redis->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + return $this->redis->setex($id, $lifeTime, $data); + } + + return $this->redis->set($id, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->redis->delete($id) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + return $this->redis->delete($keys) >= 0; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->redis->flushDB(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->redis->info(); + return [ + Cache::STATS_HITS => $info['keyspace_hits'], + Cache::STATS_MISSES => $info['keyspace_misses'], + Cache::STATS_UPTIME => $info['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false + ]; + } + + /** + * Returns the serializer constant to use. If Redis is compiled with + * igbinary support, that is used. Otherwise the default PHP serializer is + * used. + * + * @return integer One of the Redis::SERIALIZER_* constants + */ + protected function getSerializerValue() + { + if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) { + return Redis::SERIALIZER_IGBINARY; + } + + return Redis::SERIALIZER_PHP; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php new file mode 100644 index 0000000000..8a2e59ee1f --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php @@ -0,0 +1,249 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Riak\Bucket; +use Riak\Input; +use Riak\Exception; +use Riak\Object; + +/** + * Riak cache provider. + * + * @link www.doctrine-project.org + * @since 1.1 + * @author Guilherme Blanco + */ +class RiakCache extends CacheProvider +{ + const EXPIRES_HEADER = 'X-Riak-Meta-Expires'; + + /** + * @var \Riak\Bucket + */ + private $bucket; + + /** + * Sets the riak bucket instance to use. + * + * @param \Riak\Bucket $bucket + */ + public function __construct(Bucket $bucket) + { + $this->bucket = $bucket; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + try { + $response = $this->bucket->get($id); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + // Check for attempted siblings + $object = ($response->hasSiblings()) + ? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList()) + : $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return unserialize($object->getContent()); + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\ConnectionException + // - Riak\CommunicationException + // - Riak\UnexpectedResponseException + // - Riak\NotFoundException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + try { + // We only need the HEAD, not the entire object + $input = new Input\GetInput(); + + $input->setReturnHead(true); + + $response = $this->bucket->get($id, $input); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + $object = $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $object = new Object($id); + + $object->setContent(serialize($data)); + + if ($lifeTime > 0) { + $object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime)); + } + + $this->bucket->put($object); + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + try { + $this->bucket->delete($id); + + return true; + } catch (Exception\BadArgumentsException $e) { + // Key did not exist on cluster already + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\Exception\ConnectionException + // - Riak\Exception\CommunicationException + // - Riak\Exception\UnexpectedResponseException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + try { + $keyList = $this->bucket->getKeyList(); + + foreach ($keyList as $key) { + $this->bucket->delete($key); + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // Only exposed through HTTP stats API, not Protocol Buffers API + return null; + } + + /** + * Check if a given Riak Object have expired. + * + * @param \Riak\Object $object + * + * @return bool + */ + private function isExpired(Object $object) : bool + { + $metadataMap = $object->getMetadataMap(); + + return isset($metadataMap[self::EXPIRES_HEADER]) + && $metadataMap[self::EXPIRES_HEADER] < time(); + } + + /** + * On-read conflict resolution. Applied approach here is last write wins. + * Specific needs may override this method to apply alternate conflict resolutions. + * + * {@internal Riak does not attempt to resolve a write conflict, and store + * it as sibling of conflicted one. By following this approach, it is up to + * the next read to resolve the conflict. When this happens, your fetched + * object will have a list of siblings (read as a list of objects). + * In our specific case, we do not care about the intermediate ones since + * they are all the same read from storage, and we do apply a last sibling + * (last write) wins logic. + * If by any means our resolution generates another conflict, it'll up to + * next read to properly solve it.} + * + * @param string $id + * @param string $vClock + * @param array $objectList + * + * @return \Riak\Object + */ + protected function resolveConflict($id, $vClock, array $objectList) + { + // Our approach here is last-write wins + $winner = $objectList[count($objectList)]; + + $putInput = new Input\PutInput(); + $putInput->setVClock($vClock); + + $mergedObject = new Object($id); + $mergedObject->setContent($winner->getContent()); + + $this->bucket->put($mergedObject, $putInput); + + return $mergedObject; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php new file mode 100644 index 0000000000..5b98538e58 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php @@ -0,0 +1,222 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use SQLite3; +use SQLite3Result; + +/** + * SQLite3 cache provider. + * + * @since 1.4 + * @author Jake Bell + */ +class SQLite3Cache extends CacheProvider +{ + /** + * The ID field will store the cache key. + */ + const ID_FIELD = 'k'; + + /** + * The data field will store the serialized PHP value. + */ + const DATA_FIELD = 'd'; + + /** + * The expiration field will store a date value indicating when the + * cache entry should expire. + */ + const EXPIRATION_FIELD = 'e'; + + /** + * @var SQLite3 + */ + private $sqlite; + + /** + * @var string + */ + private $table; + + /** + * Constructor. + * + * Calling the constructor will ensure that the database file and table + * exist and will create both if they don't. + * + * @param SQLite3 $sqlite + * @param string $table + */ + public function __construct(SQLite3 $sqlite, $table) + { + $this->sqlite = $sqlite; + $this->table = (string) $table; + + list($id, $data, $exp) = $this->getFields(); + + return $this->sqlite->exec(sprintf( + 'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)', + $table, + $id, + $data, + $exp + )); + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $item = $this->findById($id); + + if (!$item) { + return false; + } + + return unserialize($item[self::DATA_FIELD]); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return null !== $this->findById($id, false); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $statement = $this->sqlite->prepare(sprintf( + 'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)', + $this->table, + implode(',', $this->getFields()) + )); + + $statement->bindValue(':id', $id); + $statement->bindValue(':data', serialize($data), SQLITE3_BLOB); + $statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null); + + return $statement->execute() instanceof SQLite3Result; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + list($idField) = $this->getFields(); + + $statement = $this->sqlite->prepare(sprintf( + 'DELETE FROM %s WHERE %s = :id', + $this->table, + $idField + )); + + $statement->bindValue(':id', $id); + + return $statement->execute() instanceof SQLite3Result; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table)); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // no-op. + } + + /** + * Find a single row by ID. + * + * @param mixed $id + * @param bool $includeData + * + * @return array|null + */ + private function findById($id, bool $includeData = true) : ?array + { + list($idField) = $fields = $this->getFields(); + + if (!$includeData) { + $key = array_search(static::DATA_FIELD, $fields); + unset($fields[$key]); + } + + $statement = $this->sqlite->prepare(sprintf( + 'SELECT %s FROM %s WHERE %s = :id LIMIT 1', + implode(',', $fields), + $this->table, + $idField + )); + + $statement->bindValue(':id', $id, SQLITE3_TEXT); + + $item = $statement->execute()->fetchArray(SQLITE3_ASSOC); + + if ($item === false) { + return null; + } + + if ($this->isExpired($item)) { + $this->doDelete($id); + + return null; + } + + return $item; + } + + /** + * Gets an array of the fields in our table. + * + * @return array + */ + private function getFields() : array + { + return [static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD]; + } + + /** + * Check if the item is expired. + * + * @param array $item + * + * @return bool + */ + private function isExpired(array $item) : bool + { + return isset($item[static::EXPIRATION_FIELD]) && + $item[self::EXPIRATION_FIELD] !== null && + $item[self::EXPIRATION_FIELD] < time(); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php new file mode 100644 index 0000000000..71b8b6f804 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php @@ -0,0 +1,25 @@ +. + */ + +namespace Doctrine\Common\Cache; + +class Version +{ + const VERSION = '1.7.1'; +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php new file mode 100644 index 0000000000..65e8456faa --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Void cache driver. The cache could be of use in tests where you don`t need to cache anything. + * + * @link www.doctrine-project.org + * @since 1.5 + * @author Kotlyar Maksim + */ +class VoidCache extends CacheProvider +{ + /** + * {@inheritDoc} + */ + protected function doFetch($id) + { + return false; + } + + /** + * {@inheritDoc} + */ + protected function doContains($id) + { + return false; + } + + /** + * {@inheritDoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return true; + } + + /** + * {@inheritDoc} + */ + protected function doDelete($id) + { + return true; + } + + /** + * {@inheritDoc} + */ + protected function doFlush() + { + return true; + } + + /** + * {@inheritDoc} + */ + protected function doGetStats() + { + return; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php new file mode 100644 index 0000000000..905ccbc27b --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php @@ -0,0 +1,119 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * WinCache cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class WinCacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return wincache_ucache_get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return wincache_ucache_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return wincache_ucache_set($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return wincache_ucache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return wincache_ucache_clear(); + } + + /** + * {@inheritdoc} + */ + protected function doFetchMultiple(array $keys) + { + return wincache_ucache_get($keys); + } + + /** + * {@inheritdoc} + */ + protected function doSaveMultiple(array $keysAndValues, $lifetime = 0) + { + $result = wincache_ucache_set($keysAndValues, null, $lifetime); + + return empty($result); + } + + /** + * {@inheritdoc} + */ + protected function doDeleteMultiple(array $keys) + { + $result = wincache_ucache_delete($keys); + + return is_array($result) && count($result) !== count($keys); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = wincache_ucache_info(); + $meminfo = wincache_ucache_meminfo(); + + return [ + Cache::STATS_HITS => $info['total_hit_count'], + Cache::STATS_MISSES => $info['total_miss_count'], + Cache::STATS_UPTIME => $info['total_cache_uptime'], + Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'], + Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php new file mode 100644 index 0000000000..799a5fc5bc --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php @@ -0,0 +1,112 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Xcache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class XcacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->doContains($id) ? unserialize(xcache_get($id)) : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return xcache_isset($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return xcache_set($id, serialize($data), (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return xcache_unset($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->checkAuthorization(); + + xcache_clear_cache(XC_TYPE_VAR); + + return true; + } + + /** + * Checks that xcache.admin.enable_auth is Off. + * + * @return void + * + * @throws \BadMethodCallException When xcache.admin.enable_auth is On. + */ + protected function checkAuthorization() + { + if (ini_get('xcache.admin.enable_auth')) { + throw new \BadMethodCallException( + 'To use all features of \Doctrine\Common\Cache\XcacheCache, ' + . 'you must set "xcache.admin.enable_auth" to "Off" in your php.ini.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $this->checkAuthorization(); + + $info = xcache_info(XC_TYPE_VAR, 0); + return [ + Cache::STATS_HITS => $info['hits'], + Cache::STATS_MISSES => $info['misses'], + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $info['size'], + Cache::STATS_MEMORY_AVAILABLE => $info['avail'], + ]; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php new file mode 100644 index 0000000000..6e35ac8236 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Zend Data Cache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Ralph Schindler + * @author Guilherme Blanco + */ +class ZendDataCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return zend_shm_cache_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (false !== zend_shm_cache_fetch($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return zend_shm_cache_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return zend_shm_cache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $namespace = $this->getNamespace(); + if (empty($namespace)) { + return zend_shm_cache_clear(); + } + return zend_shm_cache_clear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/instantiator/CONTRIBUTING.md b/vendor/doctrine/instantiator/CONTRIBUTING.md new file mode 100644 index 0000000000..75b84b2aa2 --- /dev/null +++ b/vendor/doctrine/instantiator/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + + * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) + * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) + * Any contribution must provide tests for additional introduced conditions + * Any un-confirmed issue needs a failing test case before being accepted + * Pull requests must be sent from a new hotfix/feature branch, not from `master`. + +## Installation + +To install the project and run the tests, you need to clone it first: + +```sh +$ git clone git://github.com/doctrine/instantiator.git +``` + +You will then need to run a composer installation: + +```sh +$ cd Instantiator +$ curl -s https://getcomposer.org/installer | php +$ php composer.phar update +``` + +## Testing + +The PHPUnit version to be used is the one installed as a dev- dependency via composer: + +```sh +$ ./vendor/bin/phpunit +``` + +Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement +won't be merged. + diff --git a/vendor/doctrine/instantiator/LICENSE b/vendor/doctrine/instantiator/LICENSE new file mode 100644 index 0000000000..4d983d1ac7 --- /dev/null +++ b/vendor/doctrine/instantiator/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/instantiator/README.md b/vendor/doctrine/instantiator/README.md new file mode 100644 index 0000000000..b66064bf55 --- /dev/null +++ b/vendor/doctrine/instantiator/README.md @@ -0,0 +1,40 @@ +# Instantiator + +This library provides a way of avoiding usage of constructors when instantiating PHP classes. + +[![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator) +[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/instantiator/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/instantiator/?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/instantiator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/instantiator/?branch=master) +[![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator) +[![HHVM Status](http://hhvm.h4cc.de/badge/doctrine/instantiator.png)](http://hhvm.h4cc.de/package/doctrine/instantiator) + +[![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator) +[![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator) + +## Installation + +The suggested installation method is via [composer](https://getcomposer.org/): + +```sh +php composer.phar require "doctrine/instantiator:~1.0.3" +``` + +## Usage + +The instantiator is able to create new instances of any class without using the constructor or any API of the class +itself: + +```php +$instantiator = new \Doctrine\Instantiator\Instantiator(); + +$instance = $instantiator->instantiate(\My\ClassName\Here::class); +``` + +## Contributing + +Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out! + +## Credits + +This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which +has been donated to the doctrine organization, and which is now deprecated in favour of this package. diff --git a/vendor/doctrine/instantiator/composer.json b/vendor/doctrine/instantiator/composer.json new file mode 100644 index 0000000000..403ee8e609 --- /dev/null +++ b/vendor/doctrine/instantiator/composer.json @@ -0,0 +1,45 @@ +{ + "name": "doctrine/instantiator", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "type": "library", + "license": "MIT", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "instantiate", + "constructor" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "require": { + "php": "^7.1" + }, + "require-dev": { + "ext-phar": "*", + "ext-pdo": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2", + "athletic/athletic": "~0.1.8" + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "autoload-dev": { + "psr-0": { + "DoctrineTest\\InstantiatorPerformance\\": "tests", + "DoctrineTest\\InstantiatorTest\\": "tests", + "DoctrineTest\\InstantiatorTestAsset\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..3065375a8c --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php @@ -0,0 +1,29 @@ +. + */ + +namespace Doctrine\Instantiator\Exception; + +/** + * Base exception marker interface for the instantiator component + * + * @author Marco Pivetta + */ +interface ExceptionInterface +{ +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..cb57aa86fe --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php @@ -0,0 +1,52 @@ +. + */ + +namespace Doctrine\Instantiator\Exception; + +use InvalidArgumentException as BaseInvalidArgumentException; +use ReflectionClass; + +/** + * Exception for invalid arguments provided to the instantiator + * + * @author Marco Pivetta + */ +class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface +{ + public static function fromNonExistingClass(string $className) : self + { + if (interface_exists($className)) { + return new self(sprintf('The provided type "%s" is an interface, and can not be instantiated', $className)); + } + + if (PHP_VERSION_ID >= 50400 && trait_exists($className)) { + return new self(sprintf('The provided type "%s" is a trait, and can not be instantiated', $className)); + } + + return new self(sprintf('The provided class "%s" does not exist', $className)); + } + + public static function fromAbstractClass(ReflectionClass $reflectionClass) : self + { + return new self(sprintf( + 'The provided class "%s" is abstract, and can not be instantiated', + $reflectionClass->getName() + )); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php new file mode 100644 index 0000000000..2b704b9202 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php @@ -0,0 +1,66 @@ +. + */ + +namespace Doctrine\Instantiator\Exception; + +use Exception; +use ReflectionClass; +use UnexpectedValueException as BaseUnexpectedValueException; + +/** + * Exception for given parameters causing invalid/unexpected state on instantiation + * + * @author Marco Pivetta + */ +class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface +{ + public static function fromSerializationTriggeredException( + ReflectionClass $reflectionClass, + Exception $exception + ) : self { + return new self( + sprintf( + 'An exception was raised while trying to instantiate an instance of "%s" via un-serialization', + $reflectionClass->getName() + ), + 0, + $exception + ); + } + + public static function fromUncleanUnSerialization( + ReflectionClass $reflectionClass, + string $errorString, + int $errorCode, + string $errorFile, + int $errorLine + ) : self { + return new self( + sprintf( + 'Could not produce an instance of "%s" via un-serialization, since an error was triggered ' + . 'in file "%s" at line "%d"', + $reflectionClass->getName(), + $errorFile, + $errorLine + ), + 0, + new Exception($errorString, $errorCode) + ); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php new file mode 100644 index 0000000000..69fe65da34 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php @@ -0,0 +1,216 @@ +. + */ + +namespace Doctrine\Instantiator; + +use Doctrine\Instantiator\Exception\InvalidArgumentException; +use Doctrine\Instantiator\Exception\UnexpectedValueException; +use Exception; +use ReflectionClass; + +/** + * {@inheritDoc} + * + * @author Marco Pivetta + */ +final class Instantiator implements InstantiatorInterface +{ + /** + * Markers used internally by PHP to define whether {@see \unserialize} should invoke + * the method {@see \Serializable::unserialize()} when dealing with classes implementing + * the {@see \Serializable} interface. + */ + const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C'; + const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O'; + + /** + * @var \callable[] used to instantiate specific classes, indexed by class name + */ + private static $cachedInstantiators = []; + + /** + * @var object[] of objects that can directly be cloned, indexed by class name + */ + private static $cachedCloneables = []; + + /** + * {@inheritDoc} + */ + public function instantiate($className) + { + if (isset(self::$cachedCloneables[$className])) { + return clone self::$cachedCloneables[$className]; + } + + if (isset(self::$cachedInstantiators[$className])) { + $factory = self::$cachedInstantiators[$className]; + + return $factory(); + } + + return $this->buildAndCacheFromFactory($className); + } + + /** + * Builds the requested object and caches it in static properties for performance + * + * @return object + */ + private function buildAndCacheFromFactory(string $className) + { + $factory = self::$cachedInstantiators[$className] = $this->buildFactory($className); + $instance = $factory(); + + if ($this->isSafeToClone(new ReflectionClass($instance))) { + self::$cachedCloneables[$className] = clone $instance; + } + + return $instance; + } + + /** + * Builds a callable capable of instantiating the given $className without + * invoking its constructor. + * + * @throws InvalidArgumentException + * @throws UnexpectedValueException + * @throws \ReflectionException + */ + private function buildFactory(string $className) : callable + { + $reflectionClass = $this->getReflectionClass($className); + + if ($this->isInstantiableViaReflection($reflectionClass)) { + return [$reflectionClass, 'newInstanceWithoutConstructor']; + } + + $serializedString = sprintf( + '%s:%d:"%s":0:{}', + self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER, + strlen($className), + $className + ); + + $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString); + + return function () use ($serializedString) { + return unserialize($serializedString); + }; + } + + /** + * @param string $className + * + * @return ReflectionClass + * + * @throws InvalidArgumentException + * @throws \ReflectionException + */ + private function getReflectionClass($className) : ReflectionClass + { + if (! class_exists($className)) { + throw InvalidArgumentException::fromNonExistingClass($className); + } + + $reflection = new ReflectionClass($className); + + if ($reflection->isAbstract()) { + throw InvalidArgumentException::fromAbstractClass($reflection); + } + + return $reflection; + } + + /** + * @param ReflectionClass $reflectionClass + * @param string $serializedString + * + * @throws UnexpectedValueException + * + * @return void + */ + private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, $serializedString) : void + { + set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) : void { + $error = UnexpectedValueException::fromUncleanUnSerialization( + $reflectionClass, + $message, + $code, + $file, + $line + ); + }); + + $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString); + + restore_error_handler(); + + if ($error) { + throw $error; + } + } + + /** + * @param ReflectionClass $reflectionClass + * @param string $serializedString + * + * @throws UnexpectedValueException + * + * @return void + */ + private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString) : void + { + try { + unserialize($serializedString); + } catch (Exception $exception) { + restore_error_handler(); + + throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception); + } + } + + private function isInstantiableViaReflection(ReflectionClass $reflectionClass) : bool + { + return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal()); + } + + /** + * Verifies whether the given class is to be considered internal + */ + private function hasInternalAncestors(ReflectionClass $reflectionClass) : bool + { + do { + if ($reflectionClass->isInternal()) { + return true; + } + } while ($reflectionClass = $reflectionClass->getParentClass()); + + return false; + } + + /** + * Checks if a class is cloneable + * + * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects. + */ + private function isSafeToClone(ReflectionClass $reflection) : bool + { + return $reflection->isCloneable() && ! $reflection->hasMethod('__clone'); + } +} diff --git a/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php new file mode 100644 index 0000000000..b665bea854 --- /dev/null +++ b/vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Instantiator; + +/** + * Instantiator provides utility methods to build objects without invoking their constructors + * + * @author Marco Pivetta + */ +interface InstantiatorInterface +{ + /** + * @param string $className + * + * @return object + * + * @throws \Doctrine\Instantiator\Exception\ExceptionInterface + */ + public function instantiate($className); +} diff --git a/vendor/firebase/php-jwt/LICENSE b/vendor/firebase/php-jwt/LICENSE new file mode 100644 index 0000000000..cb0c49b331 --- /dev/null +++ b/vendor/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Neuman Vong nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/firebase/php-jwt/README.md b/vendor/firebase/php-jwt/README.md new file mode 100644 index 0000000000..b1a7a3a206 --- /dev/null +++ b/vendor/firebase/php-jwt/README.md @@ -0,0 +1,200 @@ +[![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt) +[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) +[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) +[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +composer require firebase/php-jwt +``` + +Example +------- +```php + "http://example.org", + "aud" => "http://example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($token, $key); +$decoded = JWT::decode($jwt, $key, array('HS256')); + +print_r($decoded); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, $key, array('HS256')); + +?> +``` +Example with RS256 (openssl) +---------------------------- +```php + "example.org", + "aud" => "example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +$jwt = JWT::encode($token, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, $publicKey, array('RS256')); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; +echo "Decode:\n" . print_r($decoded_array, true) . "\n"; +?> +``` + +Changelog +--------- + +#### 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +#### 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +#### 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +#### 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +#### 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +#### 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. + + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +New Lines in private keys +----- + +If your private key contains `\n` characters, be sure to wrap it in double quotes `""` +and not single quotes `''` in order to properly interpret the escaped characters. + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/vendor/firebase/php-jwt/composer.json b/vendor/firebase/php-jwt/composer.json new file mode 100644 index 0000000000..b76ffd1910 --- /dev/null +++ b/vendor/firebase/php-jwt/composer.json @@ -0,0 +1,29 @@ +{ + "name": "firebase/php-jwt", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + } +} diff --git a/vendor/firebase/php-jwt/src/BeforeValidException.php b/vendor/firebase/php-jwt/src/BeforeValidException.php new file mode 100644 index 0000000000..a6ee2f7c69 --- /dev/null +++ b/vendor/firebase/php-jwt/src/BeforeValidException.php @@ -0,0 +1,7 @@ + + * @author Anant Narayanan + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + */ + public static $leeway = 0; + + /** + * Allow the current timestamp to be specified. + * Useful for fixing a value within unit testing. + * + * Will default to PHP time() value if null. + */ + public static $timestamp = null; + + public static $supported_algs = array( + 'HS256' => array('hash_hmac', 'SHA256'), + 'HS512' => array('hash_hmac', 'SHA512'), + 'HS384' => array('hash_hmac', 'SHA384'), + 'RS256' => array('openssl', 'SHA256'), + 'RS384' => array('openssl', 'SHA384'), + 'RS512' => array('openssl', 'SHA512'), + ); + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param string|array $key The key, or map of keys. + * If the algorithm used is asymmetric, this is the public key + * @param array $allowed_algs List of supported verification algorithms + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return object The JWT's payload as a PHP object + * + * @throws UnexpectedValueException Provided JWT was invalid + * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed + * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' + * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode($jwt, $key, array $allowed_algs = array()) + { + $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp; + + if (empty($key)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = explode('.', $jwt); + if (count($tks) != 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + if (false === ($sig = static::urlsafeB64Decode($cryptob64))) { + throw new UnexpectedValueException('Invalid signature encoding'); + } + if (empty($header->alg)) { + throw new UnexpectedValueException('Empty algorithm'); + } + if (empty(static::$supported_algs[$header->alg])) { + throw new UnexpectedValueException('Algorithm not supported'); + } + if (!in_array($header->alg, $allowed_algs)) { + throw new UnexpectedValueException('Algorithm not allowed'); + } + if (is_array($key) || $key instanceof \ArrayAccess) { + if (isset($header->kid)) { + if (!isset($key[$header->kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + $key = $key[$header->kid]; + } else { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + } + + // Check the signature + if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // Check if the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf) + ); + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + + return $payload; + } + + /** + * Converts and signs a PHP object or array into a JWT string. + * + * @param object|array $payload PHP object or array + * @param string $key The secret key. + * If the algorithm used is asymmetric, this is the private key + * @param string $alg The signing algorithm. + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * @param mixed $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) + { + $header = array('typ' => 'JWT', 'alg' => $alg); + if ($keyId !== null) { + $header['kid'] = $keyId; + } + if ( isset($head) && is_array($head) ) { + $header = array_merge($head, $header); + } + $segments = array(); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload)); + $signing_input = implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return implode('.', $segments); + } + + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign + * @param string|resource $key The secret key + * @param string $alg The signing algorithm. + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm was specified + */ + public static function sign($msg, $key, $alg = 'HS256') + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch($function) { + case 'hash_hmac': + return hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = openssl_sign($msg, $signature, $key, $algorithm); + if (!$success) { + throw new DomainException("OpenSSL unable to sign data"); + } else { + return $signature; + } + } + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key + * @param string $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm or OpenSSL failure + */ + private static function verify($msg, $signature, $key, $alg) + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch($function) { + case 'openssl': + $success = openssl_verify($msg, $signature, $key, $algorithm); + if ($success === 1) { + return true; + } elseif ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + 'OpenSSL error: ' . openssl_error_string() + ); + case 'hash_hmac': + default: + $hash = hash_hmac($algorithm, $msg, $key, true); + if (function_exists('hash_equals')) { + return hash_equals($signature, $hash); + } + $len = min(static::safeStrlen($signature), static::safeStrlen($hash)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (ord($signature[$i]) ^ ord($hash[$i])); + } + $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash)); + + return ($status === 0); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return object Object representation of JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode($input) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { + /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you + * to specify that large ints (like Steam Transaction IDs) should be treated as + * strings, rather than the PHP default behaviour of converting them to floats. + */ + $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + } else { + /** Not all servers will support that, however, so for older versions we must + * manually detect large ints in the JSON string and quote them (thus converting + *them to strings) before decoding, hence the preg_replace() call. + */ + $max_int_length = strlen((string) PHP_INT_MAX) - 1; + $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input); + $obj = json_decode($json_without_bigints); + } + + if (function_exists('json_last_error') && $errno = json_last_error()) { + static::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP object into a JSON string. + * + * @param object|array $input A PHP object or array + * + * @return string JSON representation of the PHP object or array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode($input) + { + $json = json_encode($input); + if (function_exists('json_last_error') && $errno = json_last_error()) { + static::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + */ + public static function urlsafeB64Decode($input) + { + $remainder = strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= str_repeat('=', $padlen); + } + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode($input) + { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @return void + */ + private static function handleJsonError($errno) + { + $messages = array( + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ); + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string + * + * @return int + */ + private static function safeStrlen($str) + { + if (function_exists('mb_strlen')) { + return mb_strlen($str, '8bit'); + } + return strlen($str); + } +} diff --git a/vendor/firebase/php-jwt/src/SignatureInvalidException.php b/vendor/firebase/php-jwt/src/SignatureInvalidException.php new file mode 100644 index 0000000000..27332b21be --- /dev/null +++ b/vendor/firebase/php-jwt/src/SignatureInvalidException.php @@ -0,0 +1,7 @@ += 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `GuzzleHttp\QueryParser` has been replaced with the + `GuzzleHttp\Psr7\parse_query`. + +## 5.2.0 - 2015-01-27 + +* Added `AppliesHeadersInterface` to make applying headers to a request based + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + RingBridge. +* Added a guard in the Pool class to not use recursion for request retries. + +## 5.1.0 - 2014-12-19 + +* Pool class no longer uses recursion when a request is intercepted. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle + specific exceptions if necessary. + +## 5.0.3 - 2014-11-03 + +This change updates query strings so that they are treated as un-encoded values +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +string that should not be parsed or encoded (unless a call to getQuery() is +subsequently made, forcing the query-string to be converted into a Query +object). + +## 5.0.2 - 2014-10-30 + +* Added a trailing `\r\n` to multipart/form-data payloads. See + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * Note: This has been changed in 5.0.3 to now encode query string values by + default unless the `rawString` argument is provided when setting the query + string on a URL: Now allowing many more characters to be present in the + query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* Fixed an issue where transfer statistics were not being populated in the + RingBridge. https://github.com/guzzle/guzzle/issues/866 + +## 5.0.0 - 2014-10-12 + +Adding support for non-blocking responses and some minor API cleanup. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + argument. They now accept an associative array of options, including the + "size" key and "metadata" key which can be used to provide custom metadata. + +## 4.2.2 - 2014-09-08 + +* Fixed a memory leak in the CurlAdapter when reusing cURL handles. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 4.0.0 - 2014-03-29 + +* For more information on the 4.0 transition, see: + http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/ +* For information on changes and upgrading, see: + https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* Added `GuzzleHttp\batch()` as a convenience function for sending requests in + parallel without needing to write asynchronous code. +* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 3.4.0 - 2013-04-11 + +* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 +* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 +* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/vendor/guzzlehttp/guzzle/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000000..50a177b032 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000000..bcd18b8e71 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,91 @@ +Guzzle, PHP HTTP client +======================= + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/travis/guzzle/guzzle.svg?style=flat-square)](https://travis-ci.org/guzzle/guzzle) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Abstracts away the underlying HTTP transport, allowing you to write + environment and transport agnostic code; i.e., no hard dependency on cURL, + PHP streams, sockets, or non-blocking event loops. +- Middleware system allows you to augment and compose client behavior. + +```php +$client = new \GuzzleHttp\Client(); +$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); +echo $res->getStatusCode(); +// 200 +echo $res->getHeaderLine('content-type'); +// 'application/json; charset=utf8' +echo $res->getBody(); +// '{"id": 1420053, "name": "guzzle", ...}' + +// Send an asynchronous request. +$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); +$promise = $client->sendAsync($request)->then(function ($response) { + echo 'I completed! ' . $response->getBody(); +}); +$promise->wait(); +``` + +## Help and docs + +- [Documentation](http://guzzlephp.org/) +- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest stable version of Guzzle: + +```bash +php composer.phar require guzzlehttp/guzzle +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +You can then later update Guzzle using composer: + + ```bash +composer.phar update + ``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 | +| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 | +| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/ +[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/ +[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/ diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000000..91d1dcc993 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1203 @@ +Guzzle Upgrade Guide +==================== + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$client = new GuzzleHttp\Client(['handler' => $handler]); +``` + +## POST Requests + +This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +and `multipart` request options. `form_params` is an associative array of +strings or array of strings and is used to serialize an +`application/x-www-form-urlencoded` POST request. The +[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor +is still supported, but it has now been renamed to `handler`. Instead of +passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following +functions can be used as replacements. + +- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +You can intercept a request and inject a response using the `intercept()` event +of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and +`GuzzleHttp\Event\ErrorEvent` event. + +See: http://docs.guzzlephp.org/en/latest/events.html + +## Inflection + +The `Guzzle\Inflection` namespace has been removed. This is not a core concern +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://github.com/guzzle/command Provides a high level abstraction over web + services by representing web service operations using commands. +- http://github.com/guzzle/guzzle-services Provides an implementation of + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +3.3 to 3.4 +---------- + +Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000000..1f328e308c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,44 @@ +{ + "name": "guzzlehttp/guzzle", + "type": "library", + "description": "Guzzle is a PHP HTTP client library", + "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5", + "guzzlehttp/psr7": "^1.4", + "guzzlehttp/promises": "^1.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "autoload": { + "files": ["src/functions_include.php"], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Client.php b/vendor/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 0000000000..80417918d0 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,422 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. "handler" is a + * constructor only option that cannot be overridden in per/request + * options. If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!is_callable($config['handler'])) { + throw new \InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\uri_for($config['base_uri']); + } + + $this->configureDefaults($config); + } + + public function __call($method, $args) + { + if (count($args) < 1) { + throw new \InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + + $uri = $args[0]; + $opts = isset($args[1]) ? $args[1] : []; + + return substr($method, -5) === 'Async' + ? $this->requestAsync(substr($method, 0, -5), $uri, $opts) + : $this->request($method, $uri, $opts); + } + + public function sendAsync(RequestInterface $request, array $options = []) + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + + return $this->transfer( + $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $options + ); + } + + public function send(RequestInterface $request, array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + public function requestAsync($method, $uri = '', array $options = []) + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = isset($options['headers']) ? $options['headers'] : []; + $body = isset($options['body']) ? $options['body'] : null; + $version = isset($options['version']) ? $options['version'] : '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri($uri, $options); + if (is_array($body)) { + $this->invalidBody(); + } + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + + return $this->transfer($request, $options); + } + + public function request($method, $uri = '', array $options = []) + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + public function getConfig($option = null) + { + return $option === null + ? $this->config + : (isset($this->config[$option]) ? $this->config[$option] : null); + } + + private function buildUri($uri, array $config) + { + // for BC we accept null which would otherwise fail in uri_for + $uri = Psr7\uri_for($uri === null ? '' : $uri); + + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + * + * @param array $config + */ + private function configureDefaults(array $config) + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) { + $defaults['proxy']['http'] = getenv('HTTP_PROXY'); + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = getenv('NO_PROXY')) { + $cleanedNoProxy = str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => default_user_agent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (array_keys($this->config['headers']) as $name) { + if (strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = default_user_agent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function prepareDefaults($options) + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = null; + unset($options['headers']); + } elseif (!is_array($options['headers'])) { + throw new \InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param RequestInterface $request + * @param array $options + * + * @return Promise\PromiseInterface + */ + private function transfer(RequestInterface $request, array $options) + { + // save_to -> sink + if (isset($options['save_to'])) { + $options['sink'] = $options['save_to']; + unset($options['save_to']); + } + + // exceptions -> http_errors + if (isset($options['exceptions'])) { + $options['http_errors'] = $options['exceptions']; + unset($options['exceptions']); + } + + $request = $this->applyOptions($request, $options); + $handler = $options['handler']; + + try { + return Promise\promise_for($handler($request, $options)); + } catch (\Exception $e) { + return Promise\rejection_for($e); + } + } + + /** + * Applies the array of request options to a request. + * + * @param RequestInterface $request + * @param array $options + * + * @return RequestInterface + */ + private function applyOptions(RequestInterface $request, array &$options) + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new \InvalidArgumentException('You cannot use ' + . 'form_params and multipart at the same time. Use the ' + . 'form_params option if you want to send application/' + . 'x-www-form-urlencoded requests, and the multipart ' + . 'option to send multipart/form-data requests.'); + } + $options['body'] = http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = \GuzzleHttp\json_encode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + + if (!empty($options['decode_content']) + && $options['decode_content'] !== true + ) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (is_array($options['body'])) { + $this->invalidBody(); + } + $modify['body'] = Psr7\stream_for($options['body']); + unset($options['body']); + } + + if (!empty($options['auth']) && is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' + . base64_encode("$value[0]:$value[1]"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + case 'ntlm': + $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; + $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]"; + break; + } + } + + if (isset($options['query'])) { + $value = $options['query']; + if (is_array($value)) { + $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986); + } + if (!is_string($value)) { + throw new \InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (is_bool($options['sink'])) { + throw new \InvalidArgumentException('sink must not be a boolean'); + } + } + + $request = Psr7\modify_request($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' + . $request->getBody()->getBoundary(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + if (!$request->hasHeader($k)) { + $modify['set_headers'][$k] = $v; + } + } + $request = Psr7\modify_request($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + private function invalidBody() + { + throw new \InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a POST request has been deprecated. ' + . 'Please use the "form_params" request option to send a ' + . 'application/x-www-form-urlencoded request, or the "multipart" ' + . 'request option to send a multipart/form-data request.'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000000..2dbcffa492 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * @deprecated + */ + public static function getCookieValue($value) + { + return $value; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + * @return bool + */ + public static function shouldPersist( + SetCookie $cookie, + $allowSessionCookies = false + ) { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName($name) + { + // don't allow a null name + if ($name === null) { + return null; + } + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + * + * @param RequestInterface $request + * @return string + */ + private function getCookiePathFromRequest(RequestInterface $request) + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + if (0 === $lastSlashPos = strrpos($uriPath, '/')) { + return '/'; + } + + return substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request) + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000000..2cf298a867 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,84 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * @throws \RuntimeException if the file cannot be found or created + */ + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = \GuzzleHttp\json_encode($json); + if (false === file_put_contents($filename, $jsonStr)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } elseif ($json === '') { + return; + } + + $data = \GuzzleHttp\json_decode($json, true); + if (is_array($data)) { + foreach (json_decode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000000..4497bcf03e --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,71 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save() + { + $json = []; + foreach ($this as $cookie) { + /** @var SetCookie $cookie */ + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load() + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $data = json_decode($_SESSION[$this->sessionKey], true); + if (is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000000..f6993943e7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,403 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** @var array Cookie data */ + private $data; + + /** + * Create a new SetCookie object from a string + * + * @param string $cookie Set-Cookie header string + * + * @return self + */ + public static function fromString($cookie) + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (empty($pieces[0]) || !strpos($pieces[0], '=')) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = array_replace(self::$defaults, $data); + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(time() + $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { + if ($k === 'Expires') { + $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return rtrim($str, '; '); + } + + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + */ + public function setName($name) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $domain; + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + */ + public function setPath($path) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) + { + $this->data['Secure'] = $secure; + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) + { + $this->data['Discard'] = $discard; + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) + { + $this->data['HttpOnly'] = $httpOnly; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + * + * @return bool + */ + public function matchesPath($requestPath) + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return substr($requestPath, strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() !== null && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (preg_match( + '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', + $name + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000000..427d896fb2 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,27 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + return $e instanceof RequestException + ? $e + : new RequestException($e->getMessage(), $request, null, $e); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * @param array $ctx Optional handler context. + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null, + array $ctx = [] + ) { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $ctx + ); + } + + $level = (int) floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + + $uri = $request->getUri(); + $uri = static::obfuscateUri($uri); + + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = sprintf( + '%s: `%s %s` resulted in a `%s %s` response', + $label, + $request->getMethod(), + $uri, + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = static::getResponseBodySummary($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $ctx); + } + + /** + * Get a short summary of the response + * + * Will return `null` if the response is not printable. + * + * @param ResponseInterface $response + * + * @return string|null + */ + public static function getResponseBodySummary(ResponseInterface $response) + { + $body = $response->getBody(); + + if (!$body->isSeekable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $summary = $body->read(120); + $body->rewind(); + + if ($size > 120) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) { + return null; + } + + return $summary; + } + + /** + * Obfuscates URI if there is an username and a password present + * + * @param UriInterface $uri + * + * @return UriInterface + */ + private static function obfuscateUri($uri) + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = strpos($userInfo, ':'))) { + return $uri->withUserInfo(substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + * + * @return array + */ + public function getHandlerContext() + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php new file mode 100644 index 0000000000..a77c28926c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php @@ -0,0 +1,27 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000000..7cdd340866 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,7 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options) + { + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + + $easy = new EasyHandle; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = array_replace($conf, $options['curl']); + } + + $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles + ? array_pop($this->handles) + : curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy) + { + $resource = $easy->handle; + unset($easy->handle); + + if (count($this->handles) >= $this->maxHandles) { + curl_close($resource); + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + curl_setopt($resource, CURLOPT_HEADERFUNCTION, null); + curl_setopt($resource, CURLOPT_READFUNCTION, null); + curl_setopt($resource, CURLOPT_WRITEFUNCTION, null); + curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null); + curl_reset($resource); + $this->handles[] = $resource; + } + } + + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable $handler + * @param EasyHandle $easy + * @param CurlFactoryInterface $factory Dictates how the handle is released + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public static function finish( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // Return the response if it is present and there is no error. + $factory->release($easy); + + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy) + { + $curlStats = curl_getinfo($easy->handle); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + call_user_func($easy->options['on_stats'], $stats); + } + + private static function finishError( + callable $handler, + EasyHandle $easy, + CurlFactoryInterface $factory + ) { + // Get error information and release the handle to the factory. + $ctx = [ + 'errno' => $easy->errno, + 'error' => curl_error($easy->handle), + ] + curl_getinfo($easy->handle); + $factory->release($easy); + + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) + && (!$easy->errno || $easy->errno == 65) + ) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx) + { + static $connectionErrors = [ + CURLE_OPERATION_TIMEOUTED => true, + CURLE_COULDNT_RESOLVE_HOST => true, + CURLE_COULDNT_CONNECT => true, + CURLE_SSL_CONNECT_ERROR => true, + CURLE_GOT_NOTHING => true, + ]; + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return \GuzzleHttp\Promise\rejection_for( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) + ? new ConnectException($message, $easy->request, null, $ctx) + : new RequestException($message, $easy->request, $easy->response, null, $ctx); + + return \GuzzleHttp\Promise\rejection_for($error); + } + + private function getDefaultConf(EasyHandle $easy) + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_CONNECTTIMEOUT => 150, + ]; + + if (defined('CURLOPT_PROTOCOLS')) { + $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } else { + $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf) + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[CURLOPT_NOBODY] = true; + unset( + $conf[CURLOPT_WRITEFUNCTION], + $conf[CURLOPT_READFUNCTION], + $conf[CURLOPT_FILE], + $conf[CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf) + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || + !empty($options['_body_as_string']) + ) { + $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[CURLOPT_UPLOAD] = true; + if ($size !== null) { + $conf[CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf) + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf) + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[CURLOPT_CAINFO]); + $conf[CURLOPT_SSL_VERIFYHOST] = 0; + $conf[CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[CURLOPT_SSL_VERIFYHOST] = 2; + $conf[CURLOPT_SSL_VERIFYPEER] = true; + if (is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!file_exists($options['verify'])) { + throw new \InvalidArgumentException( + "SSL CA bundle not found: {$options['verify']}" + ); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if (is_dir($options['verify']) || + (is_link($options['verify']) && is_dir(readlink($options['verify'])))) { + $conf[CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[CURLOPT_ENCODING] = $accept; + } else { + $conf[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (isset($options['sink'])) { + $sink = $options['sink']; + if (!is_string($sink)) { + $sink = \GuzzleHttp\Psr7\stream_for($sink); + } elseif (!is_dir(dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(sprintf( + 'Directory %s does not exist for sink value of %s', + dirname($sink), + $sink + )); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) { + return $sink->write($write); + }; + } else { + // Use a default temp stream if no sink was set. + $conf[CURLOPT_FILE] = fopen('php://temp', 'w+'); + $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]); + } + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $conf[CURLOPT_NOSIGNAL] = true; + } + + if (isset($options['proxy'])) { + if (!is_array($options['proxy'])) { + $conf[CURLOPT_PROXY] = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + $host = $easy->request->getUri()->getHost(); + if (!isset($options['proxy']['no']) || + !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no']) + ) { + $conf[CURLOPT_PROXY] = $options['proxy'][$scheme]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (is_array($cert)) { + $conf[CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!file_exists($cert)) { + throw new \InvalidArgumentException( + "SSL certificate not found: {$cert}" + ); + } + $conf[CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + $sslKey = $options['ssl_key']; + if (is_array($sslKey)) { + $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1]; + $sslKey = $sslKey[0]; + } + if (!file_exists($sslKey)) { + throw new \InvalidArgumentException( + "SSL private key not found: {$sslKey}" + ); + } + $conf[CURLOPT_SSLKEY] = $sslKey; + } + + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!is_callable($progress)) { + throw new \InvalidArgumentException( + 'progress client option must be callable' + ); + } + $conf[CURLOPT_NOPROGRESS] = false; + $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($progress, $args); + }; + } + + if (!empty($options['debug'])) { + $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']); + $conf[CURLOPT_VERBOSE] = true; + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + */ + private static function retryFailedRewind( + callable $handler, + EasyHandle $easy, + array $ctx + ) { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' + . 'providing an error. The request would have been retried, ' + . 'but attempting to rewind the request body failed. ' + . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' + . 'and did not succeed. The most likely reason for the failure ' + . 'is that cURL was unable to rewind the body of the request ' + . 'and subsequent retries resulted in the same error. Turn on ' + . 'the debug option to see what went wrong. See ' + . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(EasyHandle $easy) + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + + if (!is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + + return function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + $easy->createResponse(); + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return strlen($h); + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000000..b0fc236850 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,27 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options) + { + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + curl_exec($easy->handle); + $easy->errno = curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000000..2754d8e437 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,199 @@ +factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(50); + $this->selectTimeout = isset($options['select_timeout']) + ? $options['select_timeout'] : 1; + } + + public function __get($name) + { + if ($name === '_mh') { + return $this->_mh = curl_multi_init(); + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick() + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = microtime(true); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\queue()->run(); + + if ($this->active && + curl_multi_select($this->_mh, $this->selectTimeout) === -1 + ) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute() + { + $queue = P\queue(); + + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + usleep($this->timeToNext()); + } + $this->tick(); + } + } + + private function addRequest(array $entry) + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) + { + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['easy']->handle; + unset($this->delays[$id], $this->handles[$id]); + curl_multi_remove_handle($this->_mh, $handle); + curl_close($handle); + + return true; + } + + private function processMessages() + { + while ($done = curl_multi_info_read($this->_mh)) { + $id = (int) $done['handle']; + curl_multi_remove_handle($this->_mh, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + $entry['deferred']->resolve( + CurlFactory::finish( + $this, + $entry['easy'], + $this->factory + ) + ); + } + } + + private function timeToNext() + { + $currentTime = microtime(true); + $nextTime = PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return max(0, $nextTime - $currentTime) * 1000000; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000000..7754e9111b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,92 @@ +headers)) { + throw new \RuntimeException('No headers have been received'); + } + + // HTTP-version SP status-code SP reason-phrase + $startLine = explode(' ', array_shift($this->headers), 3); + $headers = \GuzzleHttp\headers_from_lines($this->headers); + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + + if (!empty($this->options['decode_content']) + && isset($normalizedKeys['content-encoding']) + ) { + $headers['x-encoded-content-encoding'] + = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] + = $headers[$normalizedKeys['content-length']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $startLine[1], + $headers, + $this->sink, + substr($startLine[0], 5), + isset($startLine[2]) ? (string) $startLine[2] : null + ); + } + + public function __get($name) + { + $msg = $name === 'handle' + ? 'The EasyHandle has been released' + : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php new file mode 100644 index 0000000000..d892061c7a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -0,0 +1,189 @@ +onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + call_user_func_array([$this, 'append'], $queue); + } + } + + public function __invoke(RequestInterface $request, array $options) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay'])) { + usleep($options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = array_shift($this->queue); + + if (isset($options['on_headers'])) { + if (!is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + + if (is_callable($response)) { + $response = call_user_func($response, $request, $options); + } + + $response = $response instanceof \Exception + ? \GuzzleHttp\Promise\rejection_for($response) + : \GuzzleHttp\Promise\promise_for($response); + + return $response->then( + function ($value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + call_user_func($this->onFulfilled, $value); + } + if (isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (is_resource($sink)) { + fwrite($sink, $contents); + } elseif (is_string($sink)) { + file_put_contents($sink, $contents); + } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + call_user_func($this->onRejected, $reason); + } + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + */ + public function append() + { + foreach (func_get_args() as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Exception + || $value instanceof PromiseInterface + || is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected a response or ' + . 'exception. Found ' . \GuzzleHttp\describe_type($value)); + } + } + } + + /** + * Get the last received request. + * + * @return RequestInterface + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + * + * @return array + */ + public function getLastOptions() + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, 0, $reason); + call_user_func($options['on_stats'], $stats); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000000..f8b00be0b9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,55 @@ +withoutHeader('Expect'); + + // Append a content-length header if body size is zero to match + // cURL's behavior. + if (0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', 0); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + $message = $e->getMessage(); + // This list can probably get more comprehensive. + if (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + || strpos($message, "couldn't connect to host") // error on HHVM + || strpos($message, "connection attempt failed") + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } + $e = RequestException::wrapException($request, $e); + $this->invokeStats($options, $request, $startTime, null, $e); + + return \GuzzleHttp\Promise\rejection_for($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + $startTime, + ResponseInterface $response = null, + $error = null + ) { + if (isset($options['on_stats'])) { + $stats = new TransferStats( + $request, + $response, + microtime(true) - $startTime, + $error, + [] + ); + call_user_func($options['on_stats'], $stats); + } + } + + private function createResponse( + RequestInterface $request, + array $options, + $stream, + $startTime + ) { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + $parts = explode(' ', array_shift($hdrs), 3); + $ver = explode('/', $parts[0])[1]; + $status = $parts[1]; + $reason = isset($parts[2]) ? $parts[2] : null; + $headers = \GuzzleHttp\headers_from_lines($hdrs); + list($stream, $headers) = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\stream_for($stream); + $sink = $stream; + + if (strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $ex = new RequestException($msg, $request, $response, $e); + return \GuzzleHttp\Promise\rejection_for($ex); + } + } + + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain( + $stream, + $sink, + $response->getHeaderLine('Content-Length') + ); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options) + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = isset($options['sink']) + ? $options['sink'] + : fopen('php://temp', 'r+'); + + return is_string($sink) + ? new Psr7\LazyOpenStream($sink, 'w+') + : Psr7\stream_for($sink); + } + + private function checkDecode(array $options, array $headers, $stream) + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream( + Psr7\stream_for($stream) + ); + $headers['x-encoded-content-encoding'] + = $headers[$normalizedKeys['content-encoding']]; + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + // Fix content-length header + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] + = $headers[$normalizedKeys['content-length']]; + + $length = (int) $stream->getSize(); + if ($length === 0) { + unset($headers[$normalizedKeys['content-length']]); + } else { + $headers[$normalizedKeys['content-length']] = [$length]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * Drains the source stream into the "sink" client option. + * + * @param StreamInterface $source + * @param StreamInterface $sink + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @return StreamInterface + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain( + StreamInterface $source, + StreamInterface $sink, + $contentLength + ) { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\copy_to_stream( + $source, + $sink, + (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new \RuntimeException(trim($message)); + } + + return $resource; + } + + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + + if (isset($options['stream_context'])) { + if (!is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $context = array_replace_recursive( + $context, + $options['stream_context'] + ); + } + + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth']) + && is_array($options['auth']) + && isset($options['auth'][2]) + && 'ntlm' == $options['auth'][2] + ) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $context = $this->createResource( + function () use ($context, $params) { + return stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $context, $options) { + $resource = fopen((string) $uri, 'r', null, $context); + $this->lastHeaders = $http_response_header; + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options) + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_A); + if (!isset($records[0]['ip'])) { + throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + $uri = $uri->withHost($records[0]['ip']); + } elseif ('v6' === $options['force_ip_resolve']) { + $records = dns_get_record($uri->getHost(), DNS_AAAA); + if (!isset($records[0]['ipv6'])) { + throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request) + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = rtrim($context['http']['header']); + + return $context; + } + + private function add_proxy(RequestInterface $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) + || !\GuzzleHttp\is_host_in_noproxy( + $request->getUri()->getHost(), + $value['no'] + ) + ) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + } + + private function add_timeout(RequestInterface $request, &$options, $value, &$params) + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + private function add_verify(RequestInterface $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + return; + } else { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(RequestInterface $request, &$options, $value, &$params) + { + if (is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + private function add_progress(RequestInterface $request, &$options, $value, &$params) + { + $this->addNotification( + $params, + function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + } + ); + } + + private function add_debug(RequestInterface $request, &$options, $value, &$params) + { + if ($value === false) { + return; + } + + static $map = [ + STREAM_NOTIFY_CONNECT => 'CONNECT', + STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + STREAM_NOTIFY_PROGRESS => 'PROGRESS', + STREAM_NOTIFY_FAILURE => 'FAILURE', + STREAM_NOTIFY_COMPLETED => 'COMPLETED', + STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + static $args = ['severity', 'message', 'message_code', + 'bytes_transferred', 'bytes_max']; + + $value = \GuzzleHttp\debug_resource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + $this->addNotification( + $params, + function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + } + ); + } + + private function addNotification(array &$params, callable $notify) + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = $this->callArray([ + $params['notification'], + $notify + ]); + } + } + + private function callArray(array $functions) + { + return function () use ($functions) { + $args = func_get_args(); + foreach ($functions as $fn) { + call_user_func_array($fn, $args); + } + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000000..24c46fd9fe --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,273 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param callable $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @param RequestInterface $request + * @param array $options + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + + return $handler($request, $options); + } + + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + if ($this->handler) { + $stack[] = "0) Handler: " . $this->debugCallable($this->handler); + } + + $result = ''; + foreach (array_reverse($this->stack) as $tuple) { + $depth++; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= "Function: " . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + + foreach (array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + + return $result; + } + + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler) + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + * + * @return bool + */ + public function hasHandler() + { + return (bool) $this->handler; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, $name = null) + { + array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, $name = '') + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after($findName, callable $middleware, $withName = '') + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove) + { + $this->cached = null; + $idx = is_callable($remove) ? 0 : 1; + $this->stack = array_values(array_filter( + $this->stack, + function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable + */ + public function resolve() + { + if (!$this->cached) { + if (!($prev = $this->handler)) { + throw new \LogicException('No handler has been specified'); + } + + foreach (array_reverse($this->stack) as $fn) { + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + /** + * @param $name + * @return int + */ + private function findByName($name) + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + * + * @param $findName + * @param $withName + * @param callable $middleware + * @param $before + */ + private function splice($findName, $withName, callable $middleware, $before) + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + array_splice($this->stack, $idx, 1, $replacement); + } + } + + /** + * Provides a debug string for a given callable. + * + * @param array|callable $fn Function to write as a string. + * + * @return string + */ + private function debugCallable($fn) + { + if (is_string($fn)) { + return "callable({$fn})"; + } + + if (is_array($fn)) { + return is_string($fn[0]) + ? "callable({$fn[0]}::{$fn[1]})" + : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; + } + + return 'callable(' . spl_object_hash($fn) . ')'; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000000..663ac73916 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,180 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + + /** @var string Template used to format log messages */ + private $template; + + /** + * @param string $template Log message template + */ + public function __construct($template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + * @param \Exception $error Exception that was received + * + * @return string + */ + public function format( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $error = null + ) { + $cache = []; + + return preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\str($request); + break; + case 'response': + $result = $response ? Psr7\str($response) : ''; + break; + case 'req_headers': + $result = trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody(); + break; + case 'res_body': + $result = $response ? $response->getBody() : 'NULL'; + break; + case 'ts': + case 'date_iso_8601': + $result = gmdate('c'); + break; + case 'date_common_log': + $result = date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(substr($matches[1], 11)); + } elseif (strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + private function headers(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . implode(', ', $values) . "\r\n"; + } + + return trim($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Middleware.php b/vendor/guzzlehttp/guzzle/src/Middleware.php new file mode 100644 index 0000000000..d4ad75c94f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Middleware.php @@ -0,0 +1,255 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + function ($response) use ($cookieJar, $request) { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_error" request option is set to true. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function httpErrors() + { + return function (callable $handler) { + return function ($request, array $options) use ($handler) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + function (ResponseInterface $response) use ($request, $handler) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable Returns a function that accepts the next handler. + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container) + { + if (!is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return function (callable $handler) use (&$container) { + return function ($request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(callable $before = null, callable $after = null) + { + return function (callable $handler) use ($before, $after) { + return function ($request, array $options) use ($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect() + { + return function (callable $handler) { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, callable $delay = null) + { + return function (callable $handler) use ($decider, $delay) { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) + { + return function (callable $handler) use ($logger, $formatter, $logLevel) { + return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + function ($response) use ($logger, $request, $formatter, $logLevel) { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + function ($reason) use ($logger, $request, $formatter) { + $response = $reason instanceof RequestException + ? $reason->getResponse() + : null; + $message = $formatter->format($request, $response, $reason); + $logger->notice($message); + return \GuzzleHttp\Promise\rejection_for($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + * + * @return callable + */ + public static function prepareBody() + { + return function (callable $handler) { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + * @return callable + */ + public static function mapRequest(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + * @return callable + */ + public static function mapResponse(callable $fn) + { + return function (callable $handler) use ($fn) { + return function ($request, array $options) use ($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000000..8f1be33cd3 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,123 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (is_callable($rfn)) { + yield $key => $rfn($opts); + } else { + throw new \InvalidArgumentException('Each value yielded by ' + . 'the iterator must be a Psr7\Http\Message\RequestInterface ' + . 'or a callable that returns a promise that fulfills ' + . 'with a Psr7\Message\Http\ResponseInterface object.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + public function promise() + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch( + ClientInterface $client, + $requests, + array $options = [] + ) { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + ksort($res); + + return $res; + } + + private static function cmpCallback(array &$options, $name, array &$results) + { + if (!isset($options[$name])) { + $options[$name] = function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000000..2eb95f9b2d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,106 @@ +nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if ($type = Psr7\mimetype_from_filename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\modify_request($request, $modify), $options); + } + + private function addExpectHeader( + RequestInterface $request, + array $options, + array &$modify + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = isset($options['expect']) ? $options['expect'] : null; + + // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 + if ($expect === false || $request->getProtocolVersion() < 1.1) { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000000..131b77179a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,237 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** @var callable */ + private $nextHandler; + + /** + * @param callable $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface|PromiseInterface $response + * + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + if (isset($options['allow_redirects']['on_redirect'])) { + call_user_func( + $options['allow_redirects']['on_redirect'], + $request, + $response, + $nextRequest->getUri() + ); + } + + /** @var PromiseInterface|ResponseInterface $promise */ + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + private function withTracking(PromiseInterface $promise, $uri, $statusCode) + { + return $promise->then( + function (ResponseInterface $response) use ($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + array_unshift($historyHeader, $uri); + array_unshift($statusHeader, $statusCode); + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + private function guardMax(RequestInterface $request, array &$options) + { + $current = isset($options['__redirect_count']) + ? $options['__redirect_count'] + : 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException( + "Will not follow more than {$max} redirects", + $request + ); + } + } + + /** + * @param RequestInterface $request + * @param array $options + * @param ResponseInterface $response + * + * @return RequestInterface + */ + public function modifyRequest( + RequestInterface $request, + array $options, + ResponseInterface $response + ) { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict']) + ) { + $modify['method'] = 'GET'; + $modify['body'] = ''; + } + + $modify['uri'] = $this->redirectUri($request, $response, $protocols); + Psr7\rewind_body($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo('', ''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization header if host is different. + if ($request->getUri()->getHost() !== $modify['uri']->getHost()) { + $modify['remove_headers'][] = 'Authorization'; + } + + return Psr7\modify_request($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + * + * @return UriInterface + */ + private function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($response->getHeaderLine('Location')) + ); + + // Ensure that the redirect URI is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + + return $location; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/vendor/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000000..c6aacfb157 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,255 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @param $retries + * + * @return int + */ + public static function exponentialDelay($retries) + { + return (int) pow(2, $retries - 1); + } + + /** + * @param RequestInterface $request + * @param array $options + * + * @return PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + private function onFulfilled(RequestInterface $req, array $options) + { + return function ($value) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + $value, + null + )) { + return $value; + } + return $this->doRetry($req, $options, $value); + }; + } + + private function onRejected(RequestInterface $req, array $options) + { + return function ($reason) use ($req, $options) { + if (!call_user_func( + $this->decider, + $options['retries'], + $req, + null, + $reason + )) { + return \GuzzleHttp\Promise\rejection_for($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) + { + $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response); + + return $this($request, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/TransferStats.php b/vendor/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000000..15f717e1ea --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,126 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + /** + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns true if a response was received. + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + * + * @return UriInterface + */ + public function getEffectiveUri() + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float Time in seconds. + */ + public function getTransferTime() + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + * + * @return array + */ + public function getHandlerStats() + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat($stat) + { + return isset($this->handlerStats[$stat]) + ? $this->handlerStats[$stat] + : null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/vendor/guzzlehttp/guzzle/src/UriTemplate.php new file mode 100644 index 0000000000..96dcfd09cd --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/UriTemplate.php @@ -0,0 +1,237 @@ + ['prefix' => '', 'joiner' => ',', 'query' => false], + '+' => ['prefix' => '', 'joiner' => ',', 'query' => false], + '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false], + '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false], + '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false], + ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true], + '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true], + '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true] + ]; + + /** @var array Delimiters */ + private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '=']; + + /** @var array Percent encoded delimiters */ + private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D']; + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = []; + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = []; + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) === '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = ['+' => '%20', '%7e' => '~']; + + $replacements = []; + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + $isAssoc = $this->isAssoc($variable); + $kvp = []; + foreach ($variable as $key => $var) { + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] === '+' || + $parsed['operator'] === '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] === '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] === '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + } else { + if ($value['modifier'] === ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] === '+' || $parsed['operator'] === '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner !== '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000000..a3ac450db9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,333 @@ +expand($template, $variables); +} + +/** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ +function describe_type($input) +{ + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } +} + +/** + * Parses an array of header lines into an associative array of headers. + * + * @param array $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ +function headers_from_lines($lines) +{ + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; +} + +/** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ +function debug_resource($value = null) +{ + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } + + return fopen('php://output', 'w'); +} + +/** + * Chooses and creates a default handler to use based on the environment. + * + * The returned handler is not wrapped by any default middlewares. + * + * @throws \RuntimeException if no viable Handler is available. + * @return callable Returns the best handler for the given system. + */ +function choose_handler() +{ + $handler = null; + if (function_exists('curl_multi_exec') && function_exists('curl_exec')) { + $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); + } elseif (function_exists('curl_exec')) { + $handler = new CurlHandler(); + } elseif (function_exists('curl_multi_exec')) { + $handler = new CurlMultiHandler(); + } + + if (ini_get('allow_url_fopen')) { + $handler = $handler + ? Proxy::wrapStreaming($handler, new StreamHandler()) + : new StreamHandler(); + } elseif (!$handler) { + throw new \RuntimeException('GuzzleHttp requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $handler; +} + +/** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ +function default_user_agent() +{ + static $defaultAgent = ''; + + if (!$defaultAgent) { + $defaultAgent = 'GuzzleHttp/' . Client::VERSION; + if (extension_loaded('curl') && function_exists('curl_version')) { + $defaultAgent .= ' curl/' . \curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; +} + +/** + * Returns the default cacert bundle for the current system. + * + * First, the openssl.cafile and curl.cainfo php.ini settings are checked. + * If those settings are not configured, then the common locations for + * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X + * and Windows are checked. If any of these file locations are found on + * disk, they will be utilized. + * + * Note: the result of this function is cached for subsequent calls. + * + * @return string + * @throws \RuntimeException if no bundle can be found. + */ +function default_ca_bundle() +{ + static $cached = null; + static $cafiles = [ + // Red Hat, CentOS, Fedora (provided by the ca-certificates package) + '/etc/pki/tls/certs/ca-bundle.crt', + // Ubuntu, Debian (provided by the ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', + // FreeBSD (provided by the ca_root_nss package) + '/usr/local/share/certs/ca-root-nss.crt', + // SLES 12 (provided by the ca-certificates package) + '/var/lib/ca-certificates/ca-bundle.pem', + // OS X provided by homebrew (using the default path) + '/usr/local/etc/openssl/cert.pem', + // Google app engine + '/etc/ca-certificates.crt', + // Windows? + 'C:\\windows\\system32\\curl-ca-bundle.crt', + 'C:\\windows\\curl-ca-bundle.crt', + ]; + + if ($cached) { + return $cached; + } + + if ($ca = ini_get('openssl.cafile')) { + return $cached = $ca; + } + + if ($ca = ini_get('curl.cainfo')) { + return $cached = $ca; + } + + foreach ($cafiles as $filename) { + if (file_exists($filename)) { + return $cached = $filename; + } + } + + throw new \RuntimeException(<<< EOT +No system CA bundle could be found in any of the the common system locations. +PHP versions earlier than 5.6 are not properly configured to use the system's +CA bundle by default. In order to verify peer certificates, you will need to +supply the path on disk to a certificate bundle to the 'verify' request +option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not +need a specific certificate bundle, then Mozilla provides a commonly used CA +bundle which can be downloaded here (provided by the maintainer of cURL): +https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once +you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP +ini setting to point to the path to the file, allowing you to omit the 'verify' +request option. See http://curl.haxx.se/docs/sslcerts.html for more +information. +EOT + ); +} + +/** + * Creates an associative array of lowercase header names to the actual + * header casing. + * + * @param array $headers + * + * @return array + */ +function normalize_header_keys(array $headers) +{ + $result = []; + foreach (array_keys($headers) as $key) { + $result[strtolower($key)] = $key; + } + + return $result; +} + +/** + * Returns true if the provided host matches any of the no proxy areas. + * + * This method will strip a port from the host if it is present. Each pattern + * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a + * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == + * "baz.foo.com", but ".foo.com" != "foo.com"). + * + * Areas are matched in the following cases: + * 1. "*" (without quotes) always matches any hosts. + * 2. An exact match. + * 3. The area starts with "." and the area is the last part of the host. e.g. + * '.mit.edu' will match any host that ends with '.mit.edu'. + * + * @param string $host Host to check against the patterns. + * @param array $noProxyArray An array of host patterns. + * + * @return bool + */ +function is_host_in_noproxy($host, array $noProxyArray) +{ + if (strlen($host) === 0) { + throw new \InvalidArgumentException('Empty host provided'); + } + + // Strip port if present. + if (strpos($host, ':')) { + $host = explode($host, ':', 2)[0]; + } + + foreach ($noProxyArray as $area) { + // Always match on wildcards. + if ($area === '*') { + return true; + } elseif (empty($area)) { + // Don't match on empty values. + continue; + } elseif ($area === $host) { + // Exact matches. + return true; + } else { + // Special match if the area when prefixed with ".". Remove any + // existing leading "." and add a new leading ".". + $area = '.' . ltrim($area, '.'); + if (substr($host, -(strlen($area))) === $area) { + return true; + } + } + } + + return false; +} + +/** + * Wrapper for json_decode that throws when an error occurs. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be decoded. + * @link http://www.php.net/manual/en/function.json-decode.php + */ +function json_decode($json, $assoc = false, $depth = 512, $options = 0) +{ + $data = \json_decode($json, $assoc, $depth, $options); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException( + 'json_decode error: ' . json_last_error_msg() + ); + } + + return $data; +} + +/** + * Wrapper for JSON encoding that throws when an error occurs. + * + * @param mixed $value The value being encoded + * @param int $options JSON encode option bitmask + * @param int $depth Set the maximum depth. Must be greater than zero. + * + * @return string + * @throws \InvalidArgumentException if the JSON cannot be encoded. + * @link http://www.php.net/manual/en/function.json-encode.php + */ +function json_encode($value, $options = 0, $depth = 512) +{ + $json = \json_encode($value, $options, $depth); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException( + 'json_encode error: ' . json_last_error_msg() + ); + } + + return $json; +} diff --git a/vendor/guzzlehttp/guzzle/src/functions_include.php b/vendor/guzzlehttp/guzzle/src/functions_include.php new file mode 100644 index 0000000000..a93393acc4 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions_include.php @@ -0,0 +1,6 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/promises/Makefile b/vendor/guzzlehttp/promises/Makefile new file mode 100644 index 0000000000..8d5b3ef95e --- /dev/null +++ b/vendor/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000000..7b607e28b1 --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,504 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +# Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\coroutine()`. + + +# Quick start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + + +## Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promises triggers callbacks +registered with the promises's `then` method. These callbacks are triggered +only once and in the order in which they were added. + + +## Resolving a promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader". +$promise->resolve('reader.'); +``` + + +## Promise forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +## Promise rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +## Rejection forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new \Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + +# Synchronous wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new \Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + + +## Unwrapping a promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +# Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +# API + + +## Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +## FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +## RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +# Promise interop + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new \GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +## Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = \GuzzleHttp\Promise\queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +# Implementation notes + + +## Promise resolution and chaining is handled iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + + +## A promise is the deferred. + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000000..ec41a61e6e --- /dev/null +++ b/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,34 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000000..6a5690c376 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,16 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + $this->nextCoroutine($this->generator->current()); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = promise_for($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(exception_for($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000000..d0ddf603fb --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,229 @@ +iterable = iter_for($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + reset($this->pending); + if (empty($this->pending) && !$this->iterable->valid()) { + $this->aggregate->resolve(null); + return; + } + + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = promise_for($this->iterable->current()); + $idx = $this->iterable->key(); + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, $value, $idx, $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, $reason, $idx, $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if ($this->aggregate->getState() !== PromiseInterface::PENDING) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000000..dbbeeb9f71 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,82 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if ($p->getState() === self::PENDING) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000000..844ada073c --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,280 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + return $onFulfilled + ? promise_for($this->result)->then($onFulfilled) + : promise_for($this->result); + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = rejection_for($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + $inner = $this->result instanceof PromiseInterface + ? $this->result->wait($unwrap) + : $this->result; + + if ($unwrap) { + if ($this->result instanceof PromiseInterface + || $this->state === self::FULFILLED + ) { + return $inner; + } else { + // It's rejected so "unwrap" and throw an exception. + throw exception_for($inner); + } + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise + && $value->getState() === self::PENDING + ) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + * + * @return array Returns the next group to resolve. + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if ($promise->getState() !== self::PENDING) { + return; + } + + try { + if (isset($handler[$index])) { + $promise->resolve($handler[$index]($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's not wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + queue()->run(); + + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + while (true) { + $result->waitIfPending(); + + if ($result->result instanceof Promise) { + $result = $result->result; + } else { + if ($result->result instanceof PromiseInterface) { + $result->result->wait(false); + } + break; + } + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000000..8f5f4b99b2 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,93 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if ($p->getState() === self::PENDING) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw exception_for($this->reason); + } + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000000..07c1136da1 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,47 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000000..6e8a2a083c --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,66 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + /** @var callable $task */ + while ($task = array_shift($this->queue)) { + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000000..ac8306e197 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,25 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ +function queue(TaskQueueInterface $assign = null) +{ + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ +function task(callable $task) +{ + $queue = queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + $promise->resolve($task()); + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + */ +function promise_for($value) +{ + if ($value instanceof PromiseInterface) { + return $value; + } + + // Return a Guzzle promise that shadows the given promise. + if (method_exists($value, 'then')) { + $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null; + $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; + $promise = new Promise($wfn, $cfn); + $value->then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ +function rejection_for($reason) +{ + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ +function exception_for($reason) +{ + return $reason instanceof \Exception || $reason instanceof \Throwable + ? $reason + : new RejectionException($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ +function iter_for($value) +{ + if ($value instanceof \Iterator) { + return $value; + } elseif (is_array($value)) { + return new \ArrayIterator($value); + } else { + return new \ArrayIterator([$value]); + } +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ +function inspect(PromiseInterface $promise) +{ + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function inspect_all($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param mixed $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ +function unwrap($promises) +{ + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function all($promises) +{ + $results = []; + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException} + * if the number of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function some($count, $promises) +{ + $results = []; + $rejections = []; + + return each( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if ($p->getState() !== PromiseInterface::PENDING) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ +function any($promises) +{ + return some(1, $promises)->then(function ($values) { return $values[0]; }); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * @see GuzzleHttp\Promise\inspect for the inspection state array format. + */ +function settle($promises) +{ + $results = []; + + return each( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator + * index, and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate promise if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); +} + +/** + * Returns true if a promise is fulfilled. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_fulfilled(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::FULFILLED; +} + +/** + * Returns true if a promise is rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_rejected(PromiseInterface $promise) +{ + return $promise->getState() === PromiseInterface::REJECTED; +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @param PromiseInterface $promise + * + * @return bool + */ +function is_settled(PromiseInterface $promise) +{ + return $promise->getState() !== PromiseInterface::PENDING; +} + +/** + * @see Coroutine + * + * @param callable $generatorFn + * + * @return PromiseInterface + */ +function coroutine(callable $generatorFn) +{ + return new Coroutine($generatorFn); +} diff --git a/vendor/guzzlehttp/promises/src/functions_include.php b/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000000..34cd1710aa --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` +* Fix compatibility of URIs with `file` scheme and empty host. +* Added common URI utility methods based on RFC 3986 (see documentation in the readme): + - `Uri::isDefaultPort` + - `Uri::isAbsolute` + - `Uri::isNetworkPathReference` + - `Uri::isAbsolutePathReference` + - `Uri::isRelativePathReference` + - `Uri::isSameDocumentReference` + - `Uri::composeComponents` + - `UriNormalizer::normalize` + - `UriNormalizer::isEquivalent` + - `UriResolver::relativize` +* Deprecated `Uri::resolve` in favor of `UriResolver::resolve` +* Deprecated `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +## 1.3.1 - 2016-06-25 + +* Fix `Uri::__toString` for network path references, e.g. `//example.org`. +* Fix missing lowercase normalization for host. +* Fix handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +* Fix `Uri::withAddedHeader` to correctly merge headers with different case. +* Fix trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +* Fix `Uri::withAddedHeader` with an array of header values. +* Fix `Uri::resolve` when base path has no slash and handling of fragment. +* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +* Fix `ServerRequest::withoutAttribute` when attribute value is null. + +## 1.3.0 - 2016-04-13 + +* Added remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +* Added support for stream_for from scalars. +* Can now extend Uri. +* Fixed a bug in validating request methods by making it more permissive. + +## 1.2.3 - 2016-02-18 + +* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +* Fixed handling of gzipped responses with FNAME headers. + +## 1.2.2 - 2016-01-22 + +* Added support for URIs without any authority. +* Added support for HTTP 451 'Unavailable For Legal Reasons.' +* Added support for using '0' as a filename. +* Added support for including non-standard ports in Host headers. + +## 1.2.1 - 2015-11-02 + +* Now supporting negative offsets when seeking to SEEK_END. + +## 1.2.0 - 2015-08-15 + +* Body as `"0"` is now properly added to a response. +* Now allowing forward seeking in CachingStream. +* Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +* functions.php is now conditionally required. +* user-info is no longer dropped when resolving URIs. + +## 1.1.0 - 2015-06-24 + +* URIs can now be relative. +* `multipart/form-data` headers are now overridden case-insensitively. +* URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +* A port is no longer added to a URI when the scheme is missing and no port is + present. + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000000..581d95f920 --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000000..16499358ea --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,739 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + + +[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\stream_for('abc, '); +$b = Psr7\stream_for('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\stream_for(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\stream_for(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\stream_for('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate deflate or gzipped content. + +This stream decorator skips the first 10 bytes of the given stream to remove +the gzip header, converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\stream_for('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\stream_for('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Function API + +There are various functions available under the `GuzzleHttp\Psr7` namespace. + + +## `function str` + +`function str(MessageInterface $message)` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\str($request); +``` + + +## `function uri_for` + +`function uri_for($uri)` + +This function accepts a string or `Psr\Http\Message\UriInterface` and returns a +UriInterface for the given value. If the value is already a `UriInterface`, it +is returned as-is. + +```php +$uri = GuzzleHttp\Psr7\uri_for('http://example.com'); +assert($uri === GuzzleHttp\Psr7\uri_for($uri)); +``` + + +## `function stream_for` + +`function stream_for($resource = '', array $options = [])` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +* - metadata: Array of custom metadata. +* - size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\stream_for('foo'); +$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r')); + +$generator function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\stream_for($generator(100)); +``` + + +## `function parse_header` + +`function parse_header($header)` + +Parse an array of header values containing ";" separated data into an array of +associative arrays representing the header key value pair data of the header. +When a parameter does not contain a value, but just contains a key, this +function will inject a key with a '' string value. + + +## `function normalize_header` + +`function normalize_header($header)` + +Converts an array of header values that may contain comma separated headers +into an array of headers with no comma separated values. + + +## `function modify_request` + +`function modify_request(RequestInterface $request, array $changes)` + +Clone and modify a request with the given changes. This method is useful for +reducing the number of clones needed to mutate a message. + +The changes can be one of: + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `function rewind_body` + +`function rewind_body(MessageInterface $message)` + +Attempts to rewind a message body and throws an exception on failure. The body +of the message will only be rewound if a call to `tell()` returns a value other +than `0`. + + +## `function try_fopen` + +`function try_fopen($filename, $mode)` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an error +handler that checks for errors and throws an exception instead. + + +## `function copy_to_string` + +`function copy_to_string(StreamInterface $stream, $maxLen = -1)` + +Copy the contents of a stream into a string until the given number of bytes +have been read. + + +## `function copy_to_stream` + +`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)` + +Copy the contents of a stream into another stream until the given number of +bytes have been read. + + +## `function hash` + +`function hash(StreamInterface $stream, $algo, $rawOutput = false)` + +Calculate a hash of a Stream. This method reads the entire stream to calculate +a rolling hash (based on PHP's hash_init functions). + + +## `function readline` + +`function readline(StreamInterface $stream, $maxLength = null)` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `function parse_request` + +`function parse_request($message)` + +Parses a request message string into a request object. + + +## `function parse_response` + +`function parse_response($message)` + +Parses a response message string into a response object. + + +## `function parse_query` + +`function parse_query($str, $urlEncoding = true)` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key value pair +will become an array. This function does not parse nested PHP style arrays into +an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into +`['foo[a]' => '1', 'foo[b]' => '2']`). + + +## `function build_query` + +`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)` + +Build a query string from an array of key value pairs. + +This function can use the return value of parse_query() to build a query string. +This function does not modify the provided keys when an array is encountered +(like http_build_query would). + + +## `function mimetype_from_filename` + +`function mimetype_from_filename($filename)` + +Determines the mimetype of a file by looking at its extension. + + +## `function mimetype_from_extension` + +`function mimetype_from_extension($extension)` + +Maps a file extensions to a mimetype. + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components. + + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string values that exactly match the +provided key are removed and replaced with the given key value pair. A value of null will set the query string +key without a value, e.g. "key" instead of "key=value". + + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the +provided key are removed. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according +to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers +do when resolving a link in a website based on the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use-case is to use the current request URI as base URI and then generate relative links in your documents +to reduce the document size or offer self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to +[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. +This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask +of normalizations to apply. The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of + ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should + not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved + characters by URI normalizers. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host + "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + Example: `file://localhost/myfile` → `file:///myfile` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would + change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes + and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be + significant (this is not defined by the standard). So this normalization is not safe and may change the semantics + of the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given +`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. +This of course assumes they will be resolved against the same base URI. If this is not the case, determination of +equivalence or difference of relative references does not mean anything. diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000000..b1c5a90ba7 --- /dev/null +++ b/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,39 @@ +{ + "name": "guzzlehttp/psr7", + "type": "library", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": ["request", "response", "message", "stream", "http", "uri", "url"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000000..23039fd794 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,233 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream + * + * {@inheritdoc} + */ + public function detach() + { + $this->close(); + $this->detached = true; + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind() + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + // Using a loose comparison here to match on '', false, and null + if ($result == null) { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000000..af4d4c2277 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,137 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + // TODO: What should happen here? + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000000..ed68f0861a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,138 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence == SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $this->read($diff); + $diff = $byte - $this->stream->getSize(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } + + private function cacheEntireStream() + { + $target = new FnStream(['write' => 'strlen']); + copy_to_stream($this, $target); + + return $this->tell(); + } +} diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000000..8935c80d72 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,42 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/FnStream.php b/vendor/guzzlehttp/psr7/src/FnStream.php new file mode 100644 index 0000000000..cc9b4453f7 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/FnStream.php @@ -0,0 +1,149 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind() + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET) + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000000..0051d3fec5 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,52 @@ +read(10); + $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header); + // Skip the header, that is 10 + length of filename + 1 (nil) bytes + $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength); + $resource = StreamWrapper::getResource($stream); + stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ); + $this->stream = new Stream($resource); + } + + /** + * @param StreamInterface $stream + * @param $header + * @return int + */ + private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header) + { + $filename_header_length = 0; + + if (substr(bin2hex($header), 6, 2) === '08') { + // we have a filename, read until nil + $filename_header_length = 1; + while ($stream->read(1) !== chr(0)) { + $filename_header_length++; + } + } + + return $filename_header_length; + } +} diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000000..02cec3af49 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,39 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return stream_for(try_fopen($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000000..3c13d4f411 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,155 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset % with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } else { + $this->stream->read($offset - $current); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000000..1e4da649ad --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,183 @@ + array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface */ + private $stream; + + public function getProtocolVersion() + { + return $this->protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($header) + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header) + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header) + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value) + { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header) + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody() + { + if (!$this->stream) { + $this->stream = stream_for(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + private function setHeaders(array $headers) + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + if (!is_array($value)) { + $value = [$value]; + } + + $value = $this->trimHeaderValues($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param string[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimHeaderValues(array $values) + { + return array_map(function ($value) { + return trim($value, " \t"); + }, $values); + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000000..c0fd584f75 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,153 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getHeaders(array $headers) + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements) + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(stream_for("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element) + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = stream_for($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if (substr($uri, 0, 6) !== 'php://') { + $element['filename'] = $uri; + } + } + + list($body, $headers) = $this->createElement( + $element['name'], + $element['contents'], + isset($element['filename']) ? $element['filename'] : null, + isset($element['headers']) ? $element['headers'] : [] + ); + + $stream->addStream(stream_for($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(stream_for("\r\n")); + } + + /** + * @return array + */ + private function createElement($name, StreamInterface $stream, $filename, array $headers) + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf('form-data; name="%s"; filename="%s"', + $name, + basename($filename)) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = mimetype_from_filename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000000..233221805e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,22 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + try { + return copy_to_string($this); + } catch (\Exception $e) { + return ''; + } + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000000..08285484da --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,142 @@ +method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = stream_for($body); + } + } + + public function getRequestTarget() + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target == '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri() + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000000..2830c6c9ee --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,132 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode = 200; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|null|resource|StreamInterface $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + $status = 200, + array $headers = [], + $body = null, + $version = '1.1', + $reason = null + ) { + $this->statusCode = (int) $status; + + if ($body !== '' && $body !== null) { + $this->stream = stream_for($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::$phrases[$this->statusCode])) { + $this->reasonPhrase = self::$phrases[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + $new = clone $this; + $new->statusCode = (int) $code; + if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) { + $reasonPhrase = self::$phrases[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + return $new; + } +} diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000000..575aab8489 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,358 @@ +serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files A array which respect $_FILES structure + * @throws InvalidArgumentException for unrecognized values + * @return array + */ + public static function normalizeFiles(array $files) + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * @return array|UploadedFileInterface + */ + private static function createUploadedFileFromSpec(array $value) + { + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + (int) $value['size'], + (int) $value['error'], + $value['name'], + $value['type'] + ); + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @param array $files + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []) + { + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + * + * @return ServerRequestInterface + */ + public static function fromGlobals() + { + $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + $headers = function_exists('getallheaders') ? getallheaders() : []; + $uri = self::getUriFromGlobals(); + $body = new LazyOpenStream('php://input', 'r+'); + $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + + return $serverRequest + ->withCookieParams($_COOKIE) + ->withQueryParams($_GET) + ->withParsedBody($_POST) + ->withUploadedFiles(self::normalizeFiles($_FILES)); + } + + /** + * Get a Uri populated with values from $_SERVER. + * + * @return UriInterface + */ + public static function getUriFromGlobals() { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + $hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']); + $uri = $uri->withHost($hostHeaderParts[0]); + if (isset($hostHeaderParts[1])) { + $hasPort = true; + $uri = $uri->withPort($hostHeaderParts[1]); + } + } elseif (isset($_SERVER['SERVER_NAME'])) { + $uri = $uri->withHost($_SERVER['SERVER_NAME']); + } elseif (isset($_SERVER['SERVER_ADDR'])) { + $uri = $uri->withHost($_SERVER['SERVER_ADDR']); + } + + if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { + $uri = $uri->withPort($_SERVER['SERVER_PORT']); + } + + $hasQuery = false; + if (isset($_SERVER['REQUEST_URI'])) { + $requestUriParts = explode('?', $_SERVER['REQUEST_URI']); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + + if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { + $uri = $uri->withQuery($_SERVER['QUERY_STRING']); + } + + return $uri; + } + + + /** + * {@inheritdoc} + */ + public function getServerParams() + { + return $this->serverParams; + } + + /** + * {@inheritdoc} + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * {@inheritdoc} + */ + public function withUploadedFiles(array $uploadedFiles) + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getCookieParams() + { + return $this->cookieParams; + } + + /** + * {@inheritdoc} + */ + public function withCookieParams(array $cookies) + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getQueryParams() + { + return $this->queryParams; + } + + /** + * {@inheritdoc} + */ + public function withQueryParams(array $query) + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + /** + * {@inheritdoc} + */ + public function withParsedBody($data) + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + /** + * {@inheritdoc} + */ + public function withAttribute($attribute, $value) + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + /** + * {@inheritdoc} + */ + public function withoutAttribute($attribute) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000000..e33662879f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,257 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ] + ]; + + /** + * This constructor accepts an associative array of options. + * + * - size: (int) If a read stream would otherwise have an indeterminate + * size, but the size is known due to foreknowledge, then you can + * provide that size, in bytes. + * - metadata: (array) Any additional metadata to return when the metadata + * of the stream is accessed. + * + * @param resource $stream Stream resource to wrap. + * @param array $options Associative array of options. + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $options = []) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + + if (isset($options['size'])) { + $this->size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + public function __get($name) + { + if ($name == 'stream') { + throw new \RuntimeException('The stream is detached'); + } + + throw new \BadMethodCallException('No value for ' . $name); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + try { + $this->seek(0); + return (string) stream_get_contents($this->stream); + } catch (\Exception $e) { + return ''; + } + } + + public function getContents() + { + $contents = stream_get_contents($this->stream); + + if ($contents === false) { + throw new \RuntimeException('Unable to read stream contents'); + } + + return $contents; + } + + public function close() + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + return !$this->stream || feof($this->stream); + } + + public function tell() + { + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } elseif (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length) + { + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + $string = fread($this->stream, $length); + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string) + { + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000000..daec6f52ea --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,149 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @param string $name Name of the property (allows "stream" only). + * + * @return StreamInterface + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return copy_to_string($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array([$this->stream, $method], $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000000..cf7b2232e4 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,121 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, stream_context_create([ + 'guzzle' => ['stream' => $stream] + ])); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + $this->stream->seek($offset, $whence); + + return true; + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'r+' => 33206, + 'w' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000000..e62bd5c807 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,316 @@ +setError($errorStatus); + $this->setSize($size); + $this->setClientFilename($clientFilename); + $this->setClientMediaType($clientMediaType); + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param mixed $streamOrFile + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile) + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @param int $error + * @throws InvalidArgumentException + */ + private function setError($error) + { + if (false === is_int($error)) { + throw new InvalidArgumentException( + 'Upload file error status must be an integer' + ); + } + + if (false === in_array($error, UploadedFile::$errors)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + /** + * @param int $size + * @throws InvalidArgumentException + */ + private function setSize($size) + { + if (false === is_int($size)) { + throw new InvalidArgumentException( + 'Upload file size must be an integer' + ); + } + + $this->size = $size; + } + + /** + * @param mixed $param + * @return boolean + */ + private function isStringOrNull($param) + { + return in_array(gettype($param), ['string', 'NULL']); + } + + /** + * @param mixed $param + * @return boolean + */ + private function isStringNotEmpty($param) + { + return is_string($param) && false === empty($param); + } + + /** + * @param string|null $clientFilename + * @throws InvalidArgumentException + */ + private function setClientFilename($clientFilename) + { + if (false === $this->isStringOrNull($clientFilename)) { + throw new InvalidArgumentException( + 'Upload file client filename must be a string or null' + ); + } + + $this->clientFilename = $clientFilename; + } + + /** + * @param string|null $clientMediaType + * @throws InvalidArgumentException + */ + private function setClientMediaType($clientMediaType) + { + if (false === $this->isStringOrNull($clientMediaType)) { + throw new InvalidArgumentException( + 'Upload file client media type must be a string or null' + ); + } + + $this->clientMediaType = $clientMediaType; + } + + /** + * Return true if there is no upload error + * + * @return boolean + */ + private function isOk() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * @return boolean + */ + public function isMoved() + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive() + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + /** + * {@inheritdoc} + * @throws RuntimeException if the upload was not successful. + */ + public function getStream() + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + return new LazyOpenStream($this->file, 'r+'); + } + + /** + * {@inheritdoc} + * + * @see http://php.net/is_uploaded_file + * @see http://php.net/move_uploaded_file + * @param string $targetPath Path to which to move the uploaded file. + * @throws RuntimeException if the upload was not successful. + * @throws InvalidArgumentException if the $path specified is invalid. + * @throws RuntimeException on any error during the move operation, or on + * the second or subsequent call to the method. + */ + public function moveTo($targetPath) + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = php_sapi_name() == 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + copy_to_stream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + /** + * {@inheritdoc} + * + * @return int|null The file size in bytes or null if unknown. + */ + public function getSize() + { + return $this->size; + } + + /** + * {@inheritdoc} + * + * @see http://php.net/manual/en/features.file-upload.errors.php + * @return int One of PHP's UPLOAD_ERR_XXX constants. + */ + public function getError() + { + return $this->error; + } + + /** + * {@inheritdoc} + * + * @return string|null The filename sent by the client or null if none + * was provided. + */ + public function getClientFilename() + { + return $this->clientFilename; + } + + /** + * {@inheritdoc} + */ + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000000..f46c1db9e0 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,702 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + private static $charUnreserved = 'a-zA-Z0-9_\-\.~'; + private static $charSubDelims = '!\$&\'\(\)\*\+,;='; + private static $replaceQuery = ['=' => '%3D', '&' => '%26']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** + * @param string $uri URI to parse + */ + public function __construct($uri = '') + { + // weak type check to also accept null until we can add scalar type hints + if ($uri != '') { + $parts = parse_url($uri); + if ($parts === false) { + throw new \InvalidArgumentException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + + public function __toString() + { + return self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @param string $scheme + * @param string $authority + * @param string $path + * @param string $query + * @param string $fragment + * + * @return string + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + * + * @param UriInterface $uri + * + * @return bool + */ + public static function isDefaultPort(UriInterface $uri) + { + return $uri->getPort() === null + || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]); + } + + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @param UriInterface $uri + * + * @return bool + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri) + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri) + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @param UriInterface $uri + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri) + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + + return ($uri->getScheme() === $base->getScheme()) + && ($uri->getAuthority() === $base->getAuthority()) + && ($uri->getPath() === $base->getPath()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @param string $path + * + * @return string + * + * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead. + * @see UriResolver::removeDotSegments + */ + public static function removeDotSegments($path) + { + return UriResolver::removeDotSegments($path); + } + + /** + * Converts the relative URI into a new URI that is resolved against the base URI. + * + * @param UriInterface $base Base URI + * @param string|UriInterface $rel Relative URI + * + * @return UriInterface + * + * @deprecated since version 1.4. Use UriResolver::resolve instead. + * @see UriResolver::resolve + */ + public static function resolve(UriInterface $base, $rel) + { + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + return UriResolver::resolve($base, $rel); + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + * + * @return UriInterface + */ + public static function withoutQueryValue(UriInterface $uri, $key) + { + $current = $uri->getQuery(); + if ($current === '') { + return $uri; + } + + $decodedKey = rawurldecode($key); + $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { + return rawurldecode(explode('=', $part)[0]) !== $decodedKey; + }); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + * + * @return UriInterface + */ + public static function withQueryValue(UriInterface $uri, $key, $value) + { + $current = $uri->getQuery(); + + if ($current === '') { + $result = []; + } else { + $decodedKey = rawurldecode($key); + $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) { + return rawurldecode(explode('=', $part)[0]) !== $decodedKey; + }); + } + + // Query string separators ("=", "&") within the key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $key = strtr($key, self::$replaceQuery); + + if ($value !== null) { + $result[] = $key . '=' . strtr($value, self::$replaceQuery); + } else { + $result[] = $key; + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @param array $parts + * + * @return UriInterface + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws \InvalidArgumentException If the components do not form a valid URI. + */ + public static function fromParts(array $parts) + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $user; + if ($password != '') { + $info .= ':' . $password; + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->validateState(); + + return $new; + } + + public function withHost($host) + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->validateState(); + + return $new; + } + + public function withPort($port) + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path) + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->validateState(); + + return $new; + } + + public function withQuery($query) + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment) + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + + $this->removeDefaultPort(); + } + + /** + * @param string $scheme + * + * @return string + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme) + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return strtolower($scheme); + } + + /** + * @param string $host + * + * @return string + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host) + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return strtolower($host); + } + + /** + * @param int|null $port + * + * @return int|null + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port) + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (1 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + ); + } + + return $port; + } + + private function removeDefaultPort() + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param string $path + * + * @return string + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path) + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param string $str + * + * @return string + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str) + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match) + { + return rawurlencode($match[0]); + } + + private function validateState() + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($this->path, '//')) { + throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + @trigger_error( + 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . + 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', + E_USER_DEPRECATED + ); + $this->path = '/'. $this->path; + //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000000..384c29e508 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,216 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + return $uri; + } + + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @return bool + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS) + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri) + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private static function decodeUnreservedCharacters(UriInterface $uri) + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + + $callback = function (array $match) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000000..c1cb8a275a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,219 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() != '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + * + * @param UriInterface $base Base URI + * @param UriInterface $target Target URI + * + * @return UriInterface The relative URI reference + */ + public static function relativize(UriInterface $base, UriInterface $target) + { + if ($target->getScheme() !== '' && + ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target) + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/functions.php b/vendor/guzzlehttp/psr7/src/functions.php new file mode 100644 index 0000000000..e40348d6ab --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/functions.php @@ -0,0 +1,828 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + return "{$msg}\r\n\r\n" . $message->getBody(); +} + +/** + * Returns a UriInterface for the given value. + * + * This function accepts a string or {@see Psr\Http\Message\UriInterface} and + * returns a UriInterface for the given value. If the value is already a + * `UriInterface`, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @return UriInterface + * @throws \InvalidArgumentException + */ +function uri_for($uri) +{ + if ($uri instanceof UriInterface) { + return $uri; + } elseif (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); +} + +/** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data + * @param array $options Additional options + * + * @return Stream + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ +function stream_for($resource = '', array $options = []) +{ + if (is_scalar($resource)) { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + return new Stream($resource, $options); + case 'object': + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return stream_for((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(fopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); +} + +/** + * Parse an array of header values containing ";" separated data into an + * array of associative arrays representing the header key value pair + * data of the header. When a parameter does not contain a value, but just + * contains a key, this function will inject a key with a '' string value. + * + * @param string|array $header Header to parse into components. + * + * @return array Returns the parsed header values. + */ +function parse_header($header) +{ + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (normalize_header($header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; +} + +/** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @return array Returns the normalized header field values. + */ +function normalize_header($header) +{ + if (!is_array($header)) { + return array_map('trim', explode(',', $header)); + } + + $result = []; + foreach ($header as $value) { + foreach ((array) $value as $v) { + if (strpos($v, ',') === false) { + $result[] = $v; + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { + $result[] = trim($vv); + } + } + } + + return $result; +} + +/** + * Clone and modify a request with the given changes. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + * + * @return RequestInterface + */ +function modify_request(RequestInterface $request, array $changes) +{ + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':'.$port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = _caseless_remove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = _caseless_remove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + return new ServerRequest( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion(), + $request->getServerParams() + ); + } + + return new Request( + isset($changes['method']) ? $changes['method'] : $request->getMethod(), + $uri, + $headers, + isset($changes['body']) ? $changes['body'] : $request->getBody(), + isset($changes['version']) + ? $changes['version'] + : $request->getProtocolVersion() + ); +} + +/** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` returns a + * value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ +function rewind_body(MessageInterface $message) +{ + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } +} + +/** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * @throws \RuntimeException if the file cannot be opened + */ +function try_fopen($filename, $mode) +{ + $ex = null; + set_error_handler(function () use ($filename, $mode, &$ex) { + $ex = new \RuntimeException(sprintf( + 'Unable to open %s using mode %s: %s', + $filename, + $mode, + func_get_args()[1] + )); + }); + + $handle = fopen($filename, $mode); + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; +} + +/** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * @return string + * @throws \RuntimeException on error. + */ +function copy_to_string(StreamInterface $stream, $maxLen = -1) +{ + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; +} + +/** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ +function copy_to_stream( + StreamInterface $source, + StreamInterface $dest, + $maxLen = -1 +) { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } +} + +/** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * @throws \RuntimeException on error. + */ +function hash( + StreamInterface $stream, + $algo, + $rawOutput = false +) { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; +} + +/** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string|bool + */ +function readline(StreamInterface $stream, $maxLength = null) +{ + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + // Using a loose equality here to match on '' and false. + if (null == ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; +} + +/** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + * + * @return Request + */ +function parse_request($message) +{ + $data = _parse_message($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); +} + +/** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * + * @return Response + */ +function parse_response($message) +{ + $data = _parse_message($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string'); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + isset($parts[2]) ? $parts[2] : null + ); +} + +/** + * Parse a query string into an associative array. + * + * If multiple values are found for the same key, the value of that key + * value pair will become an array. This function does not parse nested + * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will + * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']). + * + * @param string $str Query string to parse + * @param bool|string $urlEncoding How the query string is encoded + * + * @return array + */ +function parse_query($str, $urlEncoding = true) +{ + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($urlEncoding == PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding == PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { return $str; }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; +} + +/** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of parse_query() to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like http_build_query would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + * @return string + */ +function build_query(array $params, $encoding = PHP_QUERY_RFC3986) +{ + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function ($str) { return $str; }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder($k); + if (!is_array($v)) { + $qs .= $k; + if ($v !== null) { + $qs .= '=' . $encoder($v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + if ($vv !== null) { + $qs .= '=' . $encoder($vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; +} + +/** + * Determines the mimetype of a file by looking at its extension. + * + * @param $filename + * + * @return null|string + */ +function mimetype_from_filename($filename) +{ + return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION)); +} + +/** + * Maps a file extensions to a mimetype. + * + * @param $extension string The file extension. + * + * @return string|null + * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types + */ +function mimetype_from_extension($extension) +{ + static $mimetypes = [ + '7z' => 'application/x-7z-compressed', + 'aac' => 'audio/x-aac', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'atom' => 'application/atom+xml', + 'avi' => 'video/x-msvideo', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cer' => 'application/pkix-cert', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'deb' => 'application/x-debian-package', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dvi' => 'application/x-dvi', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'etx' => 'text/x-setext', + 'flac' => 'audio/flac', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ini' => 'text/plain', + 'iso' => 'application/x-iso9660-image', + 'jar' => 'application/java-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'latex' => 'application/x-latex', + 'log' => 'text/plain', + 'm4a' => 'audio/mp4', + 'm4v' => 'video/mp4', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4v' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'pbm' => 'image/x-portable-bitmap', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'svg' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'tar' => 'application/x-tar', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'ttf' => 'application/x-font-ttf', + 'txt' => 'text/plain', + 'wav' => 'audio/x-wav', + 'webm' => 'video/webm', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'video/x-ms-wmv', + 'woff' => 'application/x-font-woff', + 'wsdl' => 'application/wsdl+xml', + 'xbm' => 'image/x-xbitmap', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'yaml' => 'text/yaml', + 'yml' => 'text/yaml', + 'zip' => 'application/zip', + ]; + + $extension = strtolower($extension); + + return isset($mimetypes[$extension]) + ? $mimetypes[$extension] + : null; +} + +/** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + * + * @return array + * @internal + */ +function _parse_message($message) +{ + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => '']; + array_shift($lines); + + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + $line = $lines[$i]; + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $result['body'] = implode('', array_slice($lines, $i + 2)); + } + break; + } + if (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + $result['headers'][$key][] = $value; + } + } + + return $result; +} + +/** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + * + * @return string + * @internal + */ +function _parse_request_uri($path, array $headers) +{ + $hostKey = array_filter(array_keys($headers), function ($k) { + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); +} + +/** @internal */ +function _caseless_remove($keys, array $data) +{ + $result = []; + + foreach ($keys as &$key) { + $key = strtolower($key); + } + + foreach ($data as $k => $v) { + if (!in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; +} diff --git a/vendor/guzzlehttp/psr7/src/functions_include.php b/vendor/guzzlehttp/psr7/src/functions_include.php new file mode 100644 index 0000000000..96a4a83a01 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/functions_include.php @@ -0,0 +1,6 @@ +=5.4.0", + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7", + "mockery/mockery": "~0.9.2" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "minimum-stability": "stable" +} diff --git a/vendor/intervention/image/provides.json b/vendor/intervention/image/provides.json new file mode 100644 index 0000000000..a8cd1b6a54 --- /dev/null +++ b/vendor/intervention/image/provides.json @@ -0,0 +1,11 @@ +{ + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": [ + { + "alias": "Image", + "facade": "Intervention\\Image\\Facades\\Image" + } + ] +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractColor.php b/vendor/intervention/image/src/Intervention/Image/AbstractColor.php new file mode 100644 index 0000000000..28cbaf29dc --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractColor.php @@ -0,0 +1,226 @@ +parse($value); + } + + /** + * Parses given value as color + * + * @param mixed $value + * @return \Intervention\Image\AbstractColor + */ + public function parse($value) + { + switch (true) { + + case is_string($value): + $this->initFromString($value); + break; + + case is_int($value): + $this->initFromInteger($value); + break; + + case is_array($value): + $this->initFromArray($value); + break; + + case is_object($value): + $this->initFromObject($value); + break; + + case is_null($value): + $this->initFromArray([255, 255, 255, 0]); + break; + + default: + throw new \Intervention\Image\Exception\NotReadableException( + "Color format ({$value}) cannot be read." + ); + } + + return $this; + } + + /** + * Formats current color instance into given format + * + * @param string $type + * @return mixed + */ + public function format($type) + { + switch (strtolower($type)) { + + case 'rgba': + return $this->getRgba(); + + case 'hex': + return $this->getHex('#'); + + case 'int': + case 'integer': + return $this->getInt(); + + case 'array': + return $this->getArray(); + + case 'obj': + case 'object': + return $this; + + default: + throw new \Intervention\Image\Exception\NotSupportedException( + "Color format ({$type}) is not supported." + ); + } + } + + /** + * Reads RGBA values from string into array + * + * @param string $value + * @return array + */ + protected function rgbaFromString($value) + { + $result = false; + + // parse color string in hexidecimal format like #cccccc or cccccc or ccc + $hexPattern = '/^#?([a-f0-9]{1,2})([a-f0-9]{1,2})([a-f0-9]{1,2})$/i'; + + // parse color string in format rgb(140, 140, 140) + $rgbPattern = '/^rgb ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3})\)$/i'; + + // parse color string in format rgba(255, 0, 0, 0.5) + $rgbaPattern = '/^rgba ?\(([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9]{1,3}), ?([0-9.]{1,4})\)$/i'; + + if (preg_match($hexPattern, $value, $matches)) { + $result = []; + $result[0] = strlen($matches[1]) == '1' ? hexdec($matches[1].$matches[1]) : hexdec($matches[1]); + $result[1] = strlen($matches[2]) == '1' ? hexdec($matches[2].$matches[2]) : hexdec($matches[2]); + $result[2] = strlen($matches[3]) == '1' ? hexdec($matches[3].$matches[3]) : hexdec($matches[3]); + $result[3] = 1; + } elseif (preg_match($rgbPattern, $value, $matches)) { + $result = []; + $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0; + $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0; + $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0; + $result[3] = 1; + } elseif (preg_match($rgbaPattern, $value, $matches)) { + $result = []; + $result[0] = ($matches[1] >= 0 && $matches[1] <= 255) ? intval($matches[1]) : 0; + $result[1] = ($matches[2] >= 0 && $matches[2] <= 255) ? intval($matches[2]) : 0; + $result[2] = ($matches[3] >= 0 && $matches[3] <= 255) ? intval($matches[3]) : 0; + $result[3] = ($matches[4] >= 0 && $matches[4] <= 1) ? $matches[4] : 0; + } else { + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to read color ({$value})." + ); + } + + return $result; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php b/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php new file mode 100644 index 0000000000..54e1ee9d7c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractDecoder.php @@ -0,0 +1,358 @@ +data = $data; + } + + /** + * Init from given URL + * + * @param string $url + * @return \Intervention\Image\Image + */ + public function initFromUrl($url) + { + + $options = [ + 'http' => [ + 'method'=>"GET", + 'header'=>"Accept-language: en\r\n". + "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2\r\n" + ] + ]; + + $context = stream_context_create($options); + + + if ($data = @file_get_contents($url, false, $context)) { + return $this->initFromBinary($data); + } + + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to init from given url (".$url.")." + ); + } + + /** + * Init from given stream + * + * @param StreamInterface|resource $stream + * @return \Intervention\Image\Image + */ + public function initFromStream($stream) + { + if (!$stream instanceof StreamInterface) { + $stream = new Stream($stream); + } + + try { + $offset = $stream->tell(); + } catch (\RuntimeException $e) { + $offset = 0; + } + + $shouldAndCanSeek = $offset !== 0 && $stream->isSeekable(); + + if ($shouldAndCanSeek) { + $stream->rewind(); + } + + try { + $data = $stream->getContents(); + } catch (\RuntimeException $e) { + $data = null; + } + + if ($shouldAndCanSeek) { + $stream->seek($offset); + } + + if ($data) { + return $this->initFromBinary($data); + } + + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to init from given stream" + ); + } + + /** + * Determines if current source data is GD resource + * + * @return boolean + */ + public function isGdResource() + { + if (is_resource($this->data)) { + return (get_resource_type($this->data) == 'gd'); + } + + return false; + } + + /** + * Determines if current source data is Imagick object + * + * @return boolean + */ + public function isImagick() + { + return is_a($this->data, 'Imagick'); + } + + /** + * Determines if current source data is Intervention\Image\Image object + * + * @return boolean + */ + public function isInterventionImage() + { + return is_a($this->data, '\Intervention\Image\Image'); + } + + /** + * Determines if current data is SplFileInfo object + * + * @return boolean + */ + public function isSplFileInfo() + { + return is_a($this->data, 'SplFileInfo'); + } + + /** + * Determines if current data is Symfony UploadedFile component + * + * @return boolean + */ + public function isSymfonyUpload() + { + return is_a($this->data, 'Symfony\Component\HttpFoundation\File\UploadedFile'); + } + + /** + * Determines if current source data is file path + * + * @return boolean + */ + public function isFilePath() + { + if (is_string($this->data)) { + try { + return is_file($this->data); + } catch (\Exception $e) { + return false; + } + } + + return false; + } + + /** + * Determines if current source data is url + * + * @return boolean + */ + public function isUrl() + { + return (bool) filter_var($this->data, FILTER_VALIDATE_URL); + } + + /** + * Determines if current source data is a stream resource + * + * @return boolean + */ + public function isStream() + { + if ($this->data instanceof StreamInterface) return true; + if (!is_resource($this->data)) return false; + if (get_resource_type($this->data) !== 'stream') return false; + + return true; + } + + /** + * Determines if current source data is binary data + * + * @return boolean + */ + public function isBinary() + { + if (is_string($this->data)) { + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $this->data); + return (substr($mime, 0, 4) != 'text' && $mime != 'application/x-empty'); + } + + return false; + } + + /** + * Determines if current source data is data-url + * + * @return boolean + */ + public function isDataUrl() + { + $data = $this->decodeDataUrl($this->data); + + return is_null($data) ? false : true; + } + + /** + * Determines if current source data is base64 encoded + * + * @return boolean + */ + public function isBase64() + { + if (!is_string($this->data)) { + return false; + } + + return base64_encode(base64_decode($this->data)) === $this->data; + } + + /** + * Initiates new Image from Intervention\Image\Image + * + * @param Image $object + * @return \Intervention\Image\Image + */ + public function initFromInterventionImage($object) + { + return $object; + } + + /** + * Parses and decodes binary image data from data-url + * + * @param string $data_url + * @return string + */ + private function decodeDataUrl($data_url) + { + if (!is_string($data_url)) { + return null; + } + + $pattern = "/^data:(?:image\/[a-zA-Z\-\.]+)(?:charset=\".+\")?;base64,(?P.+)$/"; + preg_match($pattern, $data_url, $matches); + + if (is_array($matches) && array_key_exists('data', $matches)) { + return base64_decode($matches['data']); + } + + return null; + } + + /** + * Initiates new image from mixed data + * + * @param mixed $data + * @return \Intervention\Image\Image + */ + public function init($data) + { + $this->data = $data; + + switch (true) { + + case $this->isGdResource(): + return $this->initFromGdResource($this->data); + + case $this->isImagick(): + return $this->initFromImagick($this->data); + + case $this->isInterventionImage(): + return $this->initFromInterventionImage($this->data); + + case $this->isSplFileInfo(): + return $this->initFromPath($this->data->getRealPath()); + + case $this->isBinary(): + return $this->initFromBinary($this->data); + + case $this->isUrl(): + return $this->initFromUrl($this->data); + + case $this->isStream(): + return $this->initFromStream($this->data); + + case $this->isDataUrl(): + return $this->initFromBinary($this->decodeDataUrl($this->data)); + + case $this->isFilePath(): + return $this->initFromPath($this->data); + + // isBase64 has to be after isFilePath to prevent false positives + case $this->isBase64(): + return $this->initFromBinary(base64_decode($this->data)); + + default: + throw new Exception\NotReadableException("Image source not readable"); + } + } + + /** + * Decoder object transforms to string source data + * + * @return string + */ + public function __toString() + { + return (string) $this->data; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php b/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php new file mode 100644 index 0000000000..29ad9d5980 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractDriver.php @@ -0,0 +1,134 @@ +decoder->init($data); + } + + /** + * Encodes given image + * + * @param Image $image + * @param string $format + * @param int $quality + * @return \Intervention\Image\Image + */ + public function encode($image, $format, $quality) + { + return $this->encoder->process($image, $format, $quality); + } + + /** + * Executes named command on given image + * + * @param Image $image + * @param string $name + * @param array $arguments + * @return \Intervention\Image\Commands\AbstractCommand + */ + public function executeCommand($image, $name, $arguments) + { + $commandName = $this->getCommandClassName($name); + $command = new $commandName($arguments); + $command->execute($image); + + return $command; + } + + /** + * Returns classname of given command name + * + * @param string $name + * @return string + */ + private function getCommandClassName($name) + { + $name = mb_convert_case($name[0], MB_CASE_UPPER, 'utf-8') . mb_substr($name, 1, mb_strlen($name)); + + $drivername = $this->getDriverName(); + $classnameLocal = sprintf('\Intervention\Image\%s\Commands\%sCommand', $drivername, ucfirst($name)); + $classnameGlobal = sprintf('\Intervention\Image\Commands\%sCommand', ucfirst($name)); + + if (class_exists($classnameLocal)) { + return $classnameLocal; + } elseif (class_exists($classnameGlobal)) { + return $classnameGlobal; + } + + throw new \Intervention\Image\Exception\NotSupportedException( + "Command ({$name}) is not available for driver ({$drivername})." + ); + } + + /** + * Returns name of current driver instance + * + * @return string + */ + public function getDriverName() + { + $reflect = new \ReflectionClass($this); + $namespace = $reflect->getNamespaceName(); + + return substr(strrchr($namespace, "\\"), 1); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php b/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php new file mode 100644 index 0000000000..357b8f4875 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractEncoder.php @@ -0,0 +1,241 @@ +setImage($image); + $this->setFormat($format); + $this->setQuality($quality); + + switch (strtolower($this->format)) { + + case 'data-url': + $this->result = $this->processDataUrl(); + break; + + case 'gif': + case 'image/gif': + $this->result = $this->processGif(); + break; + + case 'png': + case 'image/png': + case 'image/x-png': + $this->result = $this->processPng(); + break; + + case 'jpg': + case 'jpeg': + case 'image/jpg': + case 'image/jpeg': + case 'image/pjpeg': + $this->result = $this->processJpeg(); + break; + + case 'tif': + case 'tiff': + case 'image/tiff': + case 'image/tif': + case 'image/x-tif': + case 'image/x-tiff': + $this->result = $this->processTiff(); + break; + + case 'bmp': + case 'bmp': + case 'ms-bmp': + case 'x-bitmap': + case 'x-bmp': + case 'x-ms-bmp': + case 'x-win-bitmap': + case 'x-windows-bmp': + case 'x-xbitmap': + case 'image/ms-bmp': + case 'image/x-bitmap': + case 'image/x-bmp': + case 'image/x-ms-bmp': + case 'image/x-win-bitmap': + case 'image/x-windows-bmp': + case 'image/x-xbitmap': + $this->result = $this->processBmp(); + break; + + case 'ico': + case 'image/x-ico': + case 'image/x-icon': + case 'image/vnd.microsoft.icon': + $this->result = $this->processIco(); + break; + + case 'psd': + case 'image/vnd.adobe.photoshop': + $this->result = $this->processPsd(); + break; + + case 'webp': + case 'image/webp': + case 'image/x-webp': + $this->result = $this->processWebp(); + break; + + default: + throw new \Intervention\Image\Exception\NotSupportedException( + "Encoding format ({$format}) is not supported." + ); + } + + $this->setImage(null); + + return $image->setEncoded($this->result); + } + + /** + * Processes and returns encoded image as data-url string + * + * @return string + */ + protected function processDataUrl() + { + $mime = $this->image->mime ? $this->image->mime : 'image/png'; + + return sprintf('data:%s;base64,%s', + $mime, + base64_encode($this->process($this->image, $mime, $this->quality)) + ); + } + + /** + * Sets image to process + * + * @param Image $image + */ + protected function setImage($image) + { + $this->image = $image; + } + + /** + * Determines output format + * + * @param string $format + */ + protected function setFormat($format = null) + { + if ($format == '' && $this->image instanceof Image) { + $format = $this->image->mime; + } + + $this->format = $format ? $format : 'jpg'; + + return $this; + } + + /** + * Determines output quality + * + * @param int $quality + */ + protected function setQuality($quality) + { + $quality = is_null($quality) ? 90 : $quality; + $quality = $quality === 0 ? 1 : $quality; + + if ($quality < 0 || $quality > 100) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + 'Quality must range from 0 to 100.' + ); + } + + $this->quality = intval($quality); + + return $this; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractFont.php b/vendor/intervention/image/src/Intervention/Image/AbstractFont.php new file mode 100644 index 0000000000..0b9b979984 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractFont.php @@ -0,0 +1,267 @@ +text = $text; + } + + /** + * Set text to be written + * + * @param String $text + * @return void + */ + public function text($text) + { + $this->text = $text; + + return $this; + } + + /** + * Get text to be written + * + * @return String + */ + public function getText() + { + return $this->text; + } + + /** + * Set font size in pixels + * + * @param int $size + * @return void + */ + public function size($size) + { + $this->size = $size; + + return $this; + } + + /** + * Get font size in pixels + * + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * Set color of text to be written + * + * @param mixed $color + * @return void + */ + public function color($color) + { + $this->color = $color; + + return $this; + } + + /** + * Get color of text + * + * @return mixed + */ + public function getColor() + { + return $this->color; + } + + /** + * Set rotation angle of text + * + * @param int $angle + * @return void + */ + public function angle($angle) + { + $this->angle = $angle; + + return $this; + } + + /** + * Get rotation angle of text + * + * @return int + */ + public function getAngle() + { + return $this->angle; + } + + /** + * Set horizontal text alignment + * + * @param string $align + * @return void + */ + public function align($align) + { + $this->align = $align; + + return $this; + } + + /** + * Get horizontal text alignment + * + * @return string + */ + public function getAlign() + { + return $this->align; + } + + /** + * Set vertical text alignment + * + * @param string $valign + * @return void + */ + public function valign($valign) + { + $this->valign = $valign; + + return $this; + } + + /** + * Get vertical text alignment + * + * @return string + */ + public function getValign() + { + return $this->valign; + } + + /** + * Set path to font file + * + * @param string $file + * @return void + */ + public function file($file) + { + $this->file = $file; + + return $this; + } + + /** + * Get path to font file + * + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * Checks if current font has access to an applicable font file + * + * @return boolean + */ + protected function hasApplicableFontFile() + { + if (is_string($this->file)) { + return file_exists($this->file); + } + + return false; + } + + /** + * Counts lines of text to be written + * + * @return int + */ + public function countLines() + { + return count(explode(PHP_EOL, $this->text)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/AbstractShape.php b/vendor/intervention/image/src/Intervention/Image/AbstractShape.php new file mode 100644 index 0000000000..cd4a9f1c68 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/AbstractShape.php @@ -0,0 +1,71 @@ +background = $color; + } + + /** + * Set border width and color of current shape + * + * @param int $width + * @param string $color + * @return void + */ + public function border($width, $color = null) + { + $this->border_width = is_numeric($width) ? intval($width) : 0; + $this->border_color = is_null($color) ? '#000000' : $color; + } + + /** + * Determines if current shape has border + * + * @return boolean + */ + public function hasBorder() + { + return ($this->border_width >= 1); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php new file mode 100644 index 0000000000..cf9ca1083a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/AbstractCommand.php @@ -0,0 +1,79 @@ +arguments = $arguments; + } + + /** + * Creates new argument instance from given argument key + * + * @param int $key + * @return \Intervention\Image\Commands\Argument + */ + public function argument($key) + { + return new \Intervention\Image\Commands\Argument($this, $key); + } + + /** + * Returns output data of current command + * + * @return mixed + */ + public function getOutput() + { + return $this->output ? $this->output : null; + } + + /** + * Determines if current instance has output data + * + * @return boolean + */ + public function hasOutput() + { + return ! is_null($this->output); + } + + /** + * Sets output data of current command + * + * @param mixed $value + */ + public function setOutput($value) + { + $this->output = $value; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php b/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php new file mode 100644 index 0000000000..40a8f629de --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/Argument.php @@ -0,0 +1,225 @@ +command = $command; + $this->key = $key; + } + + /** + * Returns name of current arguments command + * + * @return string + */ + public function getCommandName() + { + preg_match("/\\\\([\w]+)Command$/", get_class($this->command), $matches); + return isset($matches[1]) ? lcfirst($matches[1]).'()' : 'Method'; + } + + /** + * Returns value of current argument + * + * @param mixed $default + * @return mixed + */ + public function value($default = null) + { + $arguments = $this->command->arguments; + + if (is_array($arguments)) { + return isset($arguments[$this->key]) ? $arguments[$this->key] : $default; + } + + return $default; + } + + /** + * Defines current argument as required + * + * @return \Intervention\Image\Commands\Argument + */ + public function required() + { + if ( ! array_key_exists($this->key, $this->command->arguments)) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + sprintf("Missing argument %d for %s", $this->key + 1, $this->getCommandName()) + ); + } + + return $this; + } + + /** + * Determines that current argument must be of given type + * + * @return \Intervention\Image\Commands\Argument + */ + public function type($type) + { + $fail = false; + + $value = $this->value(); + + if (is_null($value)) { + return $this; + } + + switch (strtolower($type)) { + + case 'bool': + case 'boolean': + $fail = ! is_bool($value); + $message = sprintf('%s accepts only boolean values as argument %d.', $this->getCommandName(), $this->key + 1); + break; + + case 'int': + case 'integer': + $fail = ! is_integer($value); + $message = sprintf('%s accepts only integer values as argument %d.', $this->getCommandName(), $this->key + 1); + break; + + case 'num': + case 'numeric': + $fail = ! is_numeric($value); + $message = sprintf('%s accepts only numeric values as argument %d.', $this->getCommandName(), $this->key + 1); + break; + + case 'str': + case 'string': + $fail = ! is_string($value); + $message = sprintf('%s accepts only string values as argument %d.', $this->getCommandName(), $this->key + 1); + break; + + case 'array': + $fail = ! is_array($value); + $message = sprintf('%s accepts only array as argument %d.', $this->getCommandName(), $this->key + 1); + break; + + case 'closure': + $fail = ! is_a($value, '\Closure'); + $message = sprintf('%s accepts only Closure as argument %d.', $this->getCommandName(), $this->key + 1); + break; + + case 'digit': + $fail = ! $this->isDigit($value); + $message = sprintf('%s accepts only integer values as argument %d.', $this->getCommandName(), $this->key + 1); + break; + } + + if ($fail) { + + $message = isset($message) ? $message : sprintf("Missing argument for %d.", $this->key); + + throw new \Intervention\Image\Exception\InvalidArgumentException( + $message + ); + } + + return $this; + } + + /** + * Determines that current argument value must be numeric between given values + * + * @return \Intervention\Image\Commands\Argument + */ + public function between($x, $y) + { + $value = $this->type('numeric')->value(); + + if (is_null($value)) { + return $this; + } + + $alpha = min($x, $y); + $omega = max($x, $y); + + if ($value < $alpha || $value > $omega) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + sprintf('Argument %d must be between %s and %s.', $this->key, $x, $y) + ); + } + + return $this; + } + + /** + * Determines that current argument must be over a minimum value + * + * @return \Intervention\Image\Commands\Argument + */ + public function min($value) + { + $v = $this->type('numeric')->value(); + + if (is_null($v)) { + return $this; + } + + if ($v < $value) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + sprintf('Argument %d must be at least %s.', $this->key, $value) + ); + } + + return $this; + } + + /** + * Determines that current argument must be under a maxiumum value + * + * @return \Intervention\Image\Commands\Argument + */ + public function max($value) + { + $v = $this->type('numeric')->value(); + + if (is_null($v)) { + return $this; + } + + if ($v > $value) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + sprintf('Argument %d may not be greater than %s.', $this->key, $value) + ); + } + + return $this; + } + + /** + * Checks if value is "PHP" integer (120 but also 120.0) + * + * @param mixed $value + * @return boolean + */ + private function isDigit($value) + { + return is_numeric($value) ? intval($value) == $value : false; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php new file mode 100644 index 0000000000..9acc403082 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ChecksumCommand.php @@ -0,0 +1,29 @@ +getSize(); + + for ($x=0; $x <= ($size->width-1); $x++) { + for ($y=0; $y <= ($size->height-1); $y++) { + $colors[] = $image->pickColor($x, $y, 'array'); + } + } + + $this->setOutput(md5(serialize($colors))); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php new file mode 100644 index 0000000000..2fc38ddf82 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/CircleCommand.php @@ -0,0 +1,35 @@ +argument(0)->type('numeric')->required()->value(); + $x = $this->argument(1)->type('numeric')->required()->value(); + $y = $this->argument(2)->type('numeric')->required()->value(); + $callback = $this->argument(3)->type('closure')->value(); + + $circle_classname = sprintf('\Intervention\Image\%s\Shapes\CircleShape', + $image->getDriver()->getDriverName()); + + $circle = new $circle_classname($diameter); + + if ($callback instanceof Closure) { + $callback($circle); + } + + $circle->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php new file mode 100644 index 0000000000..4f364eca10 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/EllipseCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $height = $this->argument(1)->type('numeric')->required()->value(); + $x = $this->argument(2)->type('numeric')->required()->value(); + $y = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $ellipse_classname = sprintf('\Intervention\Image\%s\Shapes\EllipseShape', + $image->getDriver()->getDriverName()); + + $ellipse = new $ellipse_classname($width, $height); + + if ($callback instanceof Closure) { + $callback($ellipse); + } + + $ellipse->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php new file mode 100644 index 0000000000..2986cae8c8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ExifCommand.php @@ -0,0 +1,37 @@ +argument(0)->value(); + + // try to read exif data from image file + $data = @exif_read_data($image->dirname .'/'. $image->basename); + + if (! is_null($key) && is_array($data)) { + $data = array_key_exists($key, $data) ? $data[$key] : false; + } + + $this->setOutput($data); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php new file mode 100644 index 0000000000..88e8fd35b5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/IptcCommand.php @@ -0,0 +1,64 @@ +argument(0)->value(); + + $info = []; + @getimagesize($image->dirname .'/'. $image->basename, $info); + + $data = []; + + if (array_key_exists('APP13', $info)) { + $iptc = iptcparse($info['APP13']); + + if (is_array($iptc)) { + $data['DocumentTitle'] = isset($iptc["2#005"][0]) ? $iptc["2#005"][0] : null; + $data['Urgency'] = isset($iptc["2#010"][0]) ? $iptc["2#010"][0] : null; + $data['Category'] = isset($iptc["2#015"][0]) ? $iptc["2#015"][0] : null; + $data['Subcategories'] = isset($iptc["2#020"][0]) ? $iptc["2#020"][0] : null; + $data['Keywords'] = isset($iptc["2#025"][0]) ? $iptc["2#025"] : null; + $data['SpecialInstructions'] = isset($iptc["2#040"][0]) ? $iptc["2#040"][0] : null; + $data['CreationDate'] = isset($iptc["2#055"][0]) ? $iptc["2#055"][0] : null; + $data['CreationTime'] = isset($iptc["2#060"][0]) ? $iptc["2#060"][0] : null; + $data['AuthorByline'] = isset($iptc["2#080"][0]) ? $iptc["2#080"][0] : null; + $data['AuthorTitle'] = isset($iptc["2#085"][0]) ? $iptc["2#085"][0] : null; + $data['City'] = isset($iptc["2#090"][0]) ? $iptc["2#090"][0] : null; + $data['SubLocation'] = isset($iptc["2#092"][0]) ? $iptc["2#092"][0] : null; + $data['State'] = isset($iptc["2#095"][0]) ? $iptc["2#095"][0] : null; + $data['Country'] = isset($iptc["2#101"][0]) ? $iptc["2#101"][0] : null; + $data['OTR'] = isset($iptc["2#103"][0]) ? $iptc["2#103"][0] : null; + $data['Headline'] = isset($iptc["2#105"][0]) ? $iptc["2#105"][0] : null; + $data['Source'] = isset($iptc["2#110"][0]) ? $iptc["2#110"][0] : null; + $data['PhotoSource'] = isset($iptc["2#115"][0]) ? $iptc["2#115"][0] : null; + $data['Copyright'] = isset($iptc["2#116"][0]) ? $iptc["2#116"][0] : null; + $data['Caption'] = isset($iptc["2#120"][0]) ? $iptc["2#120"][0] : null; + $data['CaptionWriter'] = isset($iptc["2#122"][0]) ? $iptc["2#122"][0] : null; + } + } + + if (! is_null($key) && is_array($data)) { + $data = array_key_exists($key, $data) ? $data[$key] : false; + } + + $this->setOutput($data); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php new file mode 100644 index 0000000000..0089c649af --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/LineCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $y1 = $this->argument(1)->type('numeric')->required()->value(); + $x2 = $this->argument(2)->type('numeric')->required()->value(); + $y2 = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $line_classname = sprintf('\Intervention\Image\%s\Shapes\LineShape', + $image->getDriver()->getDriverName()); + + $line = new $line_classname($x2, $y2); + + if ($callback instanceof Closure) { + $callback($line); + } + + $line->applyToImage($image, $x1, $y1); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php new file mode 100644 index 0000000000..552482cb4c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/OrientateCommand.php @@ -0,0 +1,48 @@ +exif('Orientation')) { + + case 2: + $image->flip(); + break; + + case 3: + $image->rotate(180); + break; + + case 4: + $image->rotate(180)->flip(); + break; + + case 5: + $image->rotate(270)->flip(); + break; + + case 6: + $image->rotate(270); + break; + + case 7: + $image->rotate(90)->flip(); + break; + + case 8: + $image->rotate(90); + break; + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php new file mode 100644 index 0000000000..e46e3fffb3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/PolygonCommand.php @@ -0,0 +1,48 @@ +argument(0)->type('array')->required()->value(); + $callback = $this->argument(1)->type('closure')->value(); + + $vertices_count = count($points); + + // check if number if coordinates is even + if ($vertices_count % 2 !== 0) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "The number of given polygon vertices must be even." + ); + } + + if ($vertices_count < 6) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "You must have at least 3 points in your array." + ); + } + + $polygon_classname = sprintf('\Intervention\Image\%s\Shapes\PolygonShape', + $image->getDriver()->getDriverName()); + + $polygon = new $polygon_classname($points); + + if ($callback instanceof Closure) { + $callback($polygon); + } + + $polygon->applyToImage($image); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php new file mode 100644 index 0000000000..d75cd903b8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/PsrResponseCommand.php @@ -0,0 +1,45 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + //Encoded property will be populated at this moment + $stream = $image->stream($format, $quality); + + $mimetype = finfo_buffer( + finfo_open(FILEINFO_MIME_TYPE), + $image->getEncoded() + ); + + $this->setOutput(new Response( + 200, + [ + 'Content-Type' => $mimetype, + 'Content-Length' => strlen($image->getEncoded()) + ], + $stream + )); + + return true; + } +} \ No newline at end of file diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php new file mode 100644 index 0000000000..3a2074c57b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/RectangleCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('numeric')->required()->value(); + $y1 = $this->argument(1)->type('numeric')->required()->value(); + $x2 = $this->argument(2)->type('numeric')->required()->value(); + $y2 = $this->argument(3)->type('numeric')->required()->value(); + $callback = $this->argument(4)->type('closure')->value(); + + $rectangle_classname = sprintf('\Intervention\Image\%s\Shapes\RectangleShape', + $image->getDriver()->getDriverName()); + + $rectangle = new $rectangle_classname($x1, $y1, $x2, $y2); + + if ($callback instanceof Closure) { + $callback($rectangle); + } + + $rectangle->applyToImage($image, $x1, $y1); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php new file mode 100644 index 0000000000..7903b5af4b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/ResponseCommand.php @@ -0,0 +1,26 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + $response = new Response($image, $format, $quality); + + $this->setOutput($response->make()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php new file mode 100644 index 0000000000..111c475690 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/StreamCommand.php @@ -0,0 +1,25 @@ +argument(0)->value(); + $quality = $this->argument(1)->between(0, 100)->value(); + + $this->setOutput(\GuzzleHttp\Psr7\stream_for( + $image->encode($format, $quality)->getEncoded() + )); + + return true; + } +} \ No newline at end of file diff --git a/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php b/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php new file mode 100644 index 0000000000..4aebd8e892 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Commands/TextCommand.php @@ -0,0 +1,34 @@ +argument(0)->required()->value(); + $x = $this->argument(1)->type('numeric')->value(0); + $y = $this->argument(2)->type('numeric')->value(0); + $callback = $this->argument(3)->type('closure')->value(); + + $fontclassname = sprintf('\Intervention\Image\%s\Font', + $image->getDriver()->getDriverName()); + + $font = new $fontclassname($text); + + if ($callback instanceof Closure) { + $callback($font); + } + + $font->applyToImage($image, $x, $y); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Constraint.php b/vendor/intervention/image/src/Intervention/Image/Constraint.php new file mode 100644 index 0000000000..44bdd67a63 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Constraint.php @@ -0,0 +1,92 @@ +size = $size; + } + + /** + * Returns current size of constraint + * + * @return \Intervention\Image\Size + */ + public function getSize() + { + return $this->size; + } + + /** + * Fix the given argument in current constraint + * + * @param int $type + * @return void + */ + public function fix($type) + { + $this->fixed = ($this->fixed & ~(1 << $type)) | (1 << $type); + } + + /** + * Checks if given argument is fixed in current constraint + * + * @param int $type + * @return boolean + */ + public function isFixed($type) + { + return (bool) ($this->fixed & (1 << $type)); + } + + /** + * Fixes aspect ratio in current constraint + * + * @return void + */ + public function aspectRatio() + { + $this->fix(self::ASPECTRATIO); + } + + /** + * Fixes possibility to size up in current constraint + * + * @return void + */ + public function upsize() + { + $this->fix(self::UPSIZE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php b/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php new file mode 100644 index 0000000000..83e6b91f28 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Exception/ImageException.php @@ -0,0 +1,8 @@ +dirname = array_key_exists('dirname', $info) ? $info['dirname'] : null; + $this->basename = array_key_exists('basename', $info) ? $info['basename'] : null; + $this->extension = array_key_exists('extension', $info) ? $info['extension'] : null; + $this->filename = array_key_exists('filename', $info) ? $info['filename'] : null; + + if (file_exists($path) && is_file($path)) { + $this->mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); + } + + return $this; + } + + /** + * Get file size + * + * @return mixed + */ + public function filesize() + { + $path = $this->basePath(); + + if (file_exists($path) && is_file($path)) { + return filesize($path); + } + + return false; + } + + /** + * Get fully qualified path + * + * @return string + */ + public function basePath() + { + if ($this->dirname && $this->basename) { + return ($this->dirname .'/'. $this->basename); + } + + return null; + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php b/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php new file mode 100644 index 0000000000..f5aac05bf8 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Filters/DemoFilter.php @@ -0,0 +1,42 @@ +size = is_numeric($size) ? intval($size) : self::DEFAULT_SIZE; + } + + /** + * Applies filter effects to given image + * + * @param \Intervention\Image\Image $image + * @return \Intervention\Image\Image + */ + public function applyFilter(\Intervention\Image\Image $image) + { + $image->pixelate($this->size); + $image->greyscale(); + + return $image; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php b/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php new file mode 100644 index 0000000000..27c0beef4d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Filters/FilterInterface.php @@ -0,0 +1,14 @@ +a = ($value >> 24) & 0xFF; + $this->r = ($value >> 16) & 0xFF; + $this->g = ($value >> 8) & 0xFF; + $this->b = $value & 0xFF; + } + + /** + * Initiates color object from given array + * + * @param array $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromArray($array) + { + $array = array_values($array); + + if (count($array) == 4) { + + // color array with alpha value + list($r, $g, $b, $a) = $array; + $this->a = $this->alpha2gd($a); + + } elseif (count($array) == 3) { + + // color array without alpha value + list($r, $g, $b) = $array; + $this->a = 0; + + } + + $this->r = $r; + $this->g = $g; + $this->b = $b; + } + + /** + * Initiates color object from given string + * + * @param string $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromString($value) + { + if ($color = $this->rgbaFromString($value)) { + $this->r = $color[0]; + $this->g = $color[1]; + $this->b = $color[2]; + $this->a = $this->alpha2gd($color[3]); + } + } + + /** + * Initiates color object from given R, G and B values + * + * @param int $r + * @param int $g + * @param int $b + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgb($r, $g, $b) + { + $this->r = intval($r); + $this->g = intval($g); + $this->b = intval($b); + $this->a = 0; + } + + /** + * Initiates color object from given R, G, B and A values + * + * @param int $r + * @param int $g + * @param int $b + * @param float $a + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgba($r, $g, $b, $a = 1) + { + $this->r = intval($r); + $this->g = intval($g); + $this->b = intval($b); + $this->a = $this->alpha2gd($a); + } + + /** + * Initiates color object from given ImagickPixel object + * + * @param ImagickPixel $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromObject($value) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "GD colors cannot init from ImagickPixel objects." + ); + } + + /** + * Calculates integer value of current color instance + * + * @return int + */ + public function getInt() + { + return ($this->a << 24) + ($this->r << 16) + ($this->g << 8) + $this->b; + } + + /** + * Calculates hexadecimal value of current color instance + * + * @param string $prefix + * @return string + */ + public function getHex($prefix = '') + { + return sprintf('%s%02x%02x%02x', $prefix, $this->r, $this->g, $this->b); + } + + /** + * Calculates RGB(A) in array format of current color instance + * + * @return array + */ + public function getArray() + { + return [$this->r, $this->g, $this->b, round(1 - $this->a / 127, 2)]; + } + + /** + * Calculates RGBA in string format of current color instance + * + * @return string + */ + public function getRgba() + { + return sprintf('rgba(%d, %d, %d, %.2F)', $this->r, $this->g, $this->b, round(1 - $this->a / 127, 2)); + } + + /** + * Determines if current color is different from given color + * + * @param AbstractColor $color + * @param int $tolerance + * @return boolean + */ + public function differs(AbstractColor $color, $tolerance = 0) + { + $color_tolerance = round($tolerance * 2.55); + $alpha_tolerance = round($tolerance * 1.27); + + $delta = [ + 'r' => abs($color->r - $this->r), + 'g' => abs($color->g - $this->g), + 'b' => abs($color->b - $this->b), + 'a' => abs($color->a - $this->a) + ]; + + return ( + $delta['r'] > $color_tolerance or + $delta['g'] > $color_tolerance or + $delta['b'] > $color_tolerance or + $delta['a'] > $alpha_tolerance + ); + } + + /** + * Convert rgba alpha (0-1) value to gd value (0-127) + * + * @param float $input + * @return int + */ + private function alpha2gd($input) + { + $oldMin = 0; + $oldMax = 1; + + $newMin = 127; + $newMax = 0; + + return ceil(((($input- $oldMin) * ($newMax - $newMin)) / ($oldMax - $oldMin)) + $newMin); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php new file mode 100644 index 0000000000..98b3c72507 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BackupCommand.php @@ -0,0 +1,23 @@ +argument(0)->value(); + + // clone current image resource + $clone = clone $image; + $image->setBackup($clone->getCore(), $backupName); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php new file mode 100644 index 0000000000..d53f59d7c6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BlurCommand.php @@ -0,0 +1,23 @@ +argument(0)->between(0, 100)->value(1); + + for ($i=0; $i < intval($amount); $i++) { + imagefilter($image->getCore(), IMG_FILTER_GAUSSIAN_BLUR); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php new file mode 100644 index 0000000000..de4263f748 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/BrightnessCommand.php @@ -0,0 +1,19 @@ +argument(0)->between(-100, 100)->required()->value(); + + return imagefilter($image->getCore(), IMG_FILTER_BRIGHTNESS, ($level * 2.55)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php new file mode 100644 index 0000000000..8f539638b4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ColorizeCommand.php @@ -0,0 +1,27 @@ +argument(0)->between(-100, 100)->required()->value(); + $green = $this->argument(1)->between(-100, 100)->required()->value(); + $blue = $this->argument(2)->between(-100, 100)->required()->value(); + + // normalize colorize levels + $red = round($red * 2.55); + $green = round($green * 2.55); + $blue = round($blue * 2.55); + + // apply filter + return imagefilter($image->getCore(), IMG_FILTER_COLORIZE, $red, $green, $blue); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php new file mode 100644 index 0000000000..e43b761af7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ContrastCommand.php @@ -0,0 +1,19 @@ +argument(0)->between(-100, 100)->required()->value(); + + return imagefilter($image->getCore(), IMG_FILTER_CONTRAST, ($level * -1)); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php new file mode 100644 index 0000000000..b7f595421b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/CropCommand.php @@ -0,0 +1,40 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $x = $this->argument(2)->type('digit')->value(); + $y = $this->argument(3)->type('digit')->value(); + + if (is_null($width) || is_null($height)) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "Width and height of cutout needs to be defined." + ); + } + + $cropped = new Size($width, $height); + $position = new Point($x, $y); + + // align boxes + if (is_null($x) && is_null($y)) { + $position = $image->getSize()->align('center')->relativePosition($cropped->align('center')); + } + + // crop image core + return $this->modify($image, 0, 0, $position->x, $position->y, $cropped->width, $cropped->height, $cropped->width, $cropped->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php new file mode 100644 index 0000000000..18383307ac --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/DestroyCommand.php @@ -0,0 +1,25 @@ +getCore()); + + // destroy backups + foreach ($image->getBackups() as $backup) { + imagedestroy($backup); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php new file mode 100644 index 0000000000..aaecb7fb93 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FillCommand.php @@ -0,0 +1,68 @@ +argument(0)->value(); + $x = $this->argument(1)->type('digit')->value(); + $y = $this->argument(2)->type('digit')->value(); + + $width = $image->getWidth(); + $height = $image->getHeight(); + $resource = $image->getCore(); + + try { + + // set image tile filling + $source = new Decoder; + $tile = $source->init($filling); + imagesettile($image->getCore(), $tile->getCore()); + $filling = IMG_COLOR_TILED; + + } catch (\Intervention\Image\Exception\NotReadableException $e) { + + // set solid color filling + $color = new Color($filling); + $filling = $color->getInt(); + } + + imagealphablending($resource, true); + + if (is_int($x) && is_int($y)) { + + // resource should be visible through transparency + $base = $image->getDriver()->newImage($width, $height)->getCore(); + imagecopy($base, $resource, 0, 0, 0, 0, $width, $height); + + // floodfill if exact position is defined + imagefill($resource, $x, $y, $filling); + + // copy filled original over base + imagecopy($base, $resource, 0, 0, 0, 0, $width, $height); + + // set base as new resource-core + $image->setCore($base); + imagedestroy($resource); + + } else { + // fill whole image otherwise + imagefilledrectangle($resource, 0, 0, $width - 1, $height - 1, $filling); + } + + isset($tile) ? imagedestroy($tile->getCore()) : null; + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php new file mode 100644 index 0000000000..d861ad94cf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FitCommand.php @@ -0,0 +1,32 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->value($width); + $constraints = $this->argument(2)->type('closure')->value(); + $position = $this->argument(3)->type('string')->value('center'); + + // calculate size + $cropped = $image->getSize()->fit(new Size($width, $height), $position); + $resized = clone $cropped; + $resized = $resized->resize($width, $height, $constraints); + + // modify image + $this->modify($image, 0, 0, $cropped->pivot->x, $cropped->pivot->y, $resized->getWidth(), $resized->getHeight(), $cropped->getWidth(), $cropped->getHeight()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php new file mode 100644 index 0000000000..aa8f230e86 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/FlipCommand.php @@ -0,0 +1,37 @@ +argument(0)->value('h'); + + $size = $image->getSize(); + $dst = clone $size; + + switch (strtolower($mode)) { + case 2: + case 'v': + case 'vert': + case 'vertical': + $size->pivot->y = $size->height - 1; + $size->height = $size->height * (-1); + break; + + default: + $size->pivot->x = $size->width - 1; + $size->width = $size->width * (-1); + break; + } + + return $this->modify($image, 0, 0, $size->pivot->x, $size->pivot->y, $dst->width, $dst->height, $size->width, $size->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php new file mode 100644 index 0000000000..366f118089 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GammaCommand.php @@ -0,0 +1,19 @@ +argument(0)->type('numeric')->required()->value(); + + return imagegammacorrect($image->getCore(), 1, $gamma); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php new file mode 100644 index 0000000000..89ee2848f0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GetSizeCommand.php @@ -0,0 +1,24 @@ +setOutput(new Size( + imagesx($image->getCore()), + imagesy($image->getCore()) + )); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php new file mode 100644 index 0000000000..ded8e0d8f9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/GreyscaleCommand.php @@ -0,0 +1,17 @@ +getCore(), IMG_FILTER_GRAYSCALE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php new file mode 100644 index 0000000000..d31e9cde0c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/HeightenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = null; + $this->arguments[1] = $height; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php new file mode 100644 index 0000000000..eba75f012d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InsertCommand.php @@ -0,0 +1,32 @@ +argument(0)->required()->value(); + $position = $this->argument(1)->type('string')->value(); + $x = $this->argument(2)->type('digit')->value(0); + $y = $this->argument(3)->type('digit')->value(0); + + // build watermark + $watermark = $image->getDriver()->init($source); + + // define insertion point + $image_size = $image->getSize()->align($position, $x, $y); + $watermark_size = $watermark->getSize()->align($position); + $target = $image_size->relativePosition($watermark_size); + + // insert image at position + imagealphablending($image->getCore(), true); + return imagecopy($image->getCore(), $watermark->getCore(), $target->x, $target->y, 0, 0, $watermark_size->width, $watermark_size->height); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php new file mode 100644 index 0000000000..e8f4b184cf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InterlaceCommand.php @@ -0,0 +1,21 @@ +argument(0)->type('bool')->value(true); + + imageinterlace($image->getCore(), $mode); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php new file mode 100644 index 0000000000..f72e7e3050 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/InvertCommand.php @@ -0,0 +1,17 @@ +getCore(), IMG_FILTER_NEGATE); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php new file mode 100644 index 0000000000..27955e79a2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/LimitColorsCommand.php @@ -0,0 +1,51 @@ +argument(0)->value(); + $matte = $this->argument(1)->value(); + + // get current image size + $size = $image->getSize(); + + // create empty canvas + $resource = imagecreatetruecolor($size->width, $size->height); + + // define matte + if (is_null($matte)) { + $matte = imagecolorallocatealpha($resource, 255, 255, 255, 127); + } else { + $matte = $image->getDriver()->parseColor($matte)->getInt(); + } + + // fill with matte and copy original image + imagefill($resource, 0, 0, $matte); + + // set transparency + imagecolortransparent($resource, $matte); + + // copy original image + imagecopy($resource, $image->getCore(), 0, 0, 0, 0, $size->width, $size->height); + + if (is_numeric($count) && $count <= 256) { + // decrease colors + imagetruecolortopalette($resource, true, $count); + } + + // set new resource + $image->setCore($resource); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php new file mode 100644 index 0000000000..ef88d4dbae --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/MaskCommand.php @@ -0,0 +1,81 @@ +argument(0)->value(); + $mask_w_alpha = $this->argument(1)->type('bool')->value(false); + + $image_size = $image->getSize(); + + // create empty canvas + $canvas = $image->getDriver()->newImage($image_size->width, $image_size->height, [0,0,0,0]); + + // build mask image from source + $mask = $image->getDriver()->init($mask_source); + $mask_size = $mask->getSize(); + + // resize mask to size of current image (if necessary) + if ($mask_size != $image_size) { + $mask->resize($image_size->width, $image_size->height); + } + + imagealphablending($canvas->getCore(), false); + + if ( ! $mask_w_alpha) { + // mask from greyscale image + imagefilter($mask->getCore(), IMG_FILTER_GRAYSCALE); + } + + // redraw old image pixel by pixel considering alpha map + for ($x=0; $x < $image_size->width; $x++) { + for ($y=0; $y < $image_size->height; $y++) { + + $color = $image->pickColor($x, $y, 'array'); + $alpha = $mask->pickColor($x, $y, 'array'); + + if ($mask_w_alpha) { + $alpha = $alpha[3]; // use alpha channel as mask + } else { + + if ($alpha[3] == 0) { // transparent as black + $alpha = 0; + } else { + + // $alpha = floatval(round((($alpha[0] + $alpha[1] + $alpha[3]) / 3) / 255, 2)); + + // image is greyscale, so channel doesn't matter (use red channel) + $alpha = floatval(round($alpha[0] / 255, 2)); + } + + } + + // preserve alpha of original image... + if ($color[3] < $alpha) { + $alpha = $color[3]; + } + + // replace alpha value + $color[3] = $alpha; + + // redraw pixel + $canvas->pixel($color, $x, $y); + } + } + + + // replace current image with masked instance + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php new file mode 100644 index 0000000000..081e68a4a1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/OpacityCommand.php @@ -0,0 +1,29 @@ +argument(0)->between(0, 100)->required()->value(); + + // get size of image + $size = $image->getSize(); + + // build temp alpha mask + $mask_color = sprintf('rgba(0, 0, 0, %.1F)', $transparency / 100); + $mask = $image->getDriver()->newImage($size->width, $size->height, $mask_color); + + // mask image + $image->mask($mask->getCore(), true); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php new file mode 100644 index 0000000000..9fb4bb4226 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PickColorCommand.php @@ -0,0 +1,36 @@ +argument(0)->type('digit')->required()->value(); + $y = $this->argument(1)->type('digit')->required()->value(); + $format = $this->argument(2)->type('string')->value('array'); + + // pick color + $color = imagecolorat($image->getCore(), $x, $y); + + if ( ! imageistruecolor($image->getCore())) { + $color = imagecolorsforindex($image->getCore(), $color); + $color['alpha'] = round(1 - $color['alpha'] / 127, 2); + } + + $color = new Color($color); + + // format to output + $this->setOutput($color->format($format)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php new file mode 100644 index 0000000000..67f3e3b959 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelCommand.php @@ -0,0 +1,24 @@ +argument(0)->required()->value(); + $color = new Color($color); + $x = $this->argument(1)->type('digit')->required()->value(); + $y = $this->argument(2)->type('digit')->required()->value(); + + return imagesetpixel($image->getCore(), $x, $y, $color->getInt()); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php new file mode 100644 index 0000000000..2e2093d7d7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/PixelateCommand.php @@ -0,0 +1,19 @@ +argument(0)->type('digit')->value(10); + + return imagefilter($image->getCore(), IMG_FILTER_PIXELATE, $size, true); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php new file mode 100644 index 0000000000..c8d2e4a128 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResetCommand.php @@ -0,0 +1,35 @@ +argument(0)->value(); + + if (is_resource($backup = $image->getBackup($backupName))) { + + // destroy current resource + imagedestroy($image->getCore()); + + // clone backup + $backup = $image->getDriver()->cloneCore($backup); + + // reset to new resource + $image->setCore($backup); + + return true; + } + + throw new \Intervention\Image\Exception\RuntimeException( + "Backup not available. Call backup() before reset()." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php new file mode 100644 index 0000000000..70739fff9c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCanvasCommand.php @@ -0,0 +1,81 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $anchor = $this->argument(2)->value('center'); + $relative = $this->argument(3)->type('boolean')->value(false); + $bgcolor = $this->argument(4)->value(); + + $original_width = $image->getWidth(); + $original_height = $image->getHeight(); + + // check of only width or height is set + $width = is_null($width) ? $original_width : intval($width); + $height = is_null($height) ? $original_height : intval($height); + + // check on relative width/height + if ($relative) { + $width = $original_width + $width; + $height = $original_height + $height; + } + + // check for negative width/height + $width = ($width <= 0) ? $width + $original_width : $width; + $height = ($height <= 0) ? $height + $original_height : $height; + + // create new canvas + $canvas = $image->getDriver()->newImage($width, $height, $bgcolor); + + // set copy position + $canvas_size = $canvas->getSize()->align($anchor); + $image_size = $image->getSize()->align($anchor); + $canvas_pos = $image_size->relativePosition($canvas_size); + $image_pos = $canvas_size->relativePosition($image_size); + + if ($width <= $original_width) { + $dst_x = 0; + $src_x = $canvas_pos->x; + $src_w = $canvas_size->width; + } else { + $dst_x = $image_pos->x; + $src_x = 0; + $src_w = $original_width; + } + + if ($height <= $original_height) { + $dst_y = 0; + $src_y = $canvas_pos->y; + $src_h = $canvas_size->height; + } else { + $dst_y = $image_pos->y; + $src_y = 0; + $src_h = $original_height; + } + + // make image area transparent to keep transparency + // even if background-color is set + $transparent = imagecolorallocatealpha($canvas->getCore(), 255, 255, 255, 127); + imagealphablending($canvas->getCore(), false); // do not blend / just overwrite + imagefilledrectangle($canvas->getCore(), $dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1, $transparent); + + // copy image into new canvas + imagecopy($canvas->getCore(), $image->getCore(), $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h); + + // set new core to canvas + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php new file mode 100644 index 0000000000..b76df3a9f2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/ResizeCommand.php @@ -0,0 +1,82 @@ +argument(0)->value(); + $height = $this->argument(1)->value(); + $constraints = $this->argument(2)->type('closure')->value(); + + // resize box + $resized = $image->getSize()->resize($width, $height, $constraints); + + // modify image + $this->modify($image, 0, 0, 0, 0, $resized->getWidth(), $resized->getHeight(), $image->getWidth(), $image->getHeight()); + + return true; + } + + /** + * Wrapper function for 'imagecopyresampled' + * + * @param Image $image + * @param int $dst_x + * @param int $dst_y + * @param int $src_x + * @param int $src_y + * @param int $dst_w + * @param int $dst_h + * @param int $src_w + * @param int $src_h + * @return boolean + */ + protected function modify($image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) + { + // create new image + $modified = imagecreatetruecolor($dst_w, $dst_h); + + // get current image + $resource = $image->getCore(); + + // preserve transparency + $transIndex = imagecolortransparent($resource); + + if ($transIndex != -1) { + $rgba = imagecolorsforindex($modified, $transIndex); + $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); + imagefill($modified, 0, 0, $transColor); + imagecolortransparent($modified, $transColor); + } else { + imagealphablending($modified, false); + imagesavealpha($modified, true); + } + + // copy content from resource + $result = imagecopyresampled( + $modified, + $resource, + $dst_x, + $dst_y, + $src_x, + $src_y, + $dst_w, + $dst_h, + $src_w, + $src_h + ); + + // set new content as recource + $image->setCore($modified); + + return $result; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php new file mode 100644 index 0000000000..698f514a3c --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/RotateCommand.php @@ -0,0 +1,29 @@ +argument(0)->type('numeric')->required()->value(); + $color = $this->argument(1)->value(); + $color = new Color($color); + + // restrict rotations beyond 360 degrees, since the end result is the same + $angle %= 360; + + // rotate image + $image->setCore(imagerotate($image->getCore(), $angle, $color->getInt())); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php new file mode 100644 index 0000000000..4c0cc50f5a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/SharpenCommand.php @@ -0,0 +1,32 @@ +argument(0)->between(0, 100)->value(10); + + // build matrix + $min = $amount >= 10 ? $amount * -0.01 : 0; + $max = $amount * -0.025; + $abs = ((4 * $min + 4 * $max) * -1) + 1; + $div = 1; + + $matrix = [ + [$min, $max, $min], + [$max, $abs, $max], + [$min, $max, $min] + ]; + + // apply the matrix + return imageconvolution($image->getCore(), $matrix, $div, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php new file mode 100644 index 0000000000..2e36975270 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/TrimCommand.php @@ -0,0 +1,176 @@ +argument(0)->type('string')->value(); + $away = $this->argument(1)->value(); + $tolerance = $this->argument(2)->type('numeric')->value(0); + $feather = $this->argument(3)->type('numeric')->value(0); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + // default values + $checkTransparency = false; + + // define borders to trim away + if (is_null($away)) { + $away = ['top', 'right', 'bottom', 'left']; + } elseif (is_string($away)) { + $away = [$away]; + } + + // lower border names + foreach ($away as $key => $value) { + $away[$key] = strtolower($value); + } + + // define base color position + switch (strtolower($base)) { + case 'transparent': + case 'trans': + $checkTransparency = true; + $base_x = 0; + $base_y = 0; + break; + + case 'bottom-right': + case 'right-bottom': + $base_x = $width - 1; + $base_y = $height - 1; + break; + + default: + case 'top-left': + case 'left-top': + $base_x = 0; + $base_y = 0; + break; + } + + // pick base color + if ($checkTransparency) { + $color = new Color; // color will only be used to compare alpha channel + } else { + $color = $image->pickColor($base_x, $base_y, 'object'); + } + + $top_x = 0; + $top_y = 0; + $bottom_x = $width; + $bottom_y = $height; + + // search upper part of image for colors to trim away + if (in_array('top', $away)) { + + for ($y=0; $y < ceil($height/2); $y++) { + for ($x=0; $x < $width; $x++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $top_y = max(0, $y - $feather); + break 2; + } + + } + } + + } + + // search left part of image for colors to trim away + if (in_array('left', $away)) { + + for ($x=0; $x < ceil($width/2); $x++) { + for ($y=$top_y; $y < $height; $y++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $top_x = max(0, $x - $feather); + break 2; + } + + } + } + + } + + // search lower part of image for colors to trim away + if (in_array('bottom', $away)) { + + for ($y=($height-1); $y >= floor($height/2)-1; $y--) { + for ($x=$top_x; $x < $width; $x++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $bottom_y = min($height, $y+1 + $feather); + break 2; + } + + } + } + + } + + // search right part of image for colors to trim away + if (in_array('right', $away)) { + + for ($x=($width-1); $x >= floor($width/2)-1; $x--) { + for ($y=$top_y; $y < $bottom_y; $y++) { + + $checkColor = $image->pickColor($x, $y, 'object'); + + if ($checkTransparency) { + $checkColor->r = $color->r; + $checkColor->g = $color->g; + $checkColor->b = $color->b; + } + + if ($color->differs($checkColor, $tolerance)) { + $bottom_x = min($width, $x+1 + $feather); + break 2; + } + + } + } + + } + + + // trim parts of image + return $this->modify($image, 0, 0, $top_x, $top_y, ($bottom_x-$top_x), ($bottom_y-$top_y), ($bottom_x-$top_x), ($bottom_y-$top_y)); + + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php new file mode 100644 index 0000000000..43000d5dcc --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Commands/WidenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = $width; + $this->arguments[1] = null; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php b/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php new file mode 100644 index 0000000000..4fe821e5eb --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Decoder.php @@ -0,0 +1,153 @@ +gdResourceToTruecolor($core); + + // build image + $image = $this->initFromGdResource($core); + $image->mime = $mime; + $image->setFileInfoFromPath($path); + + return $image; + } + + /** + * Initiates new image from GD resource + * + * @param Resource $resource + * @return \Intervention\Image\Image + */ + public function initFromGdResource($resource) + { + return new Image(new Driver, $resource); + } + + /** + * Initiates new image from Imagick object + * + * @param Imagick $object + * @return \Intervention\Image\Image + */ + public function initFromImagick(\Imagick $object) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "Gd driver is unable to init from Imagick object." + ); + } + + /** + * Initiates new image from binary data + * + * @param string $data + * @return \Intervention\Image\Image + */ + public function initFromBinary($binary) + { + $resource = @imagecreatefromstring($binary); + + if ($resource === false) { + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to init from given binary data." + ); + } + + $image = $this->initFromGdResource($resource); + $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary); + + return $image; + } + + /** + * Transform GD resource into Truecolor version + * + * @param resource $resource + * @return bool + */ + public function gdResourceToTruecolor(&$resource) + { + $width = imagesx($resource); + $height = imagesy($resource); + + // new canvas + $canvas = imagecreatetruecolor($width, $height); + + // fill with transparent color + imagealphablending($canvas, false); + $transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127); + imagefilledrectangle($canvas, 0, 0, $width, $height, $transparent); + imagecolortransparent($canvas, $transparent); + imagealphablending($canvas, true); + + // copy original + imagecopy($canvas, $resource, 0, 0, 0, 0, $width, $height); + imagedestroy($resource); + + $resource = $canvas; + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php b/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php new file mode 100644 index 0000000000..b5a2d72ee4 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Driver.php @@ -0,0 +1,86 @@ +coreAvailable()) { + throw new \Intervention\Image\Exception\NotSupportedException( + "GD Library extension not available with this PHP installation." + ); + } + + $this->decoder = $decoder ? $decoder : new Decoder; + $this->encoder = $encoder ? $encoder : new Encoder; + } + + /** + * Creates new image instance + * + * @param int $width + * @param int $height + * @param mixed $background + * @return \Intervention\Image\Image + */ + public function newImage($width, $height, $background = null) + { + // create empty resource + $core = imagecreatetruecolor($width, $height); + $image = new \Intervention\Image\Image(new static, $core); + + // set background color + $background = new Color($background); + imagefill($image->getCore(), 0, 0, $background->getInt()); + + return $image; + } + + /** + * Reads given string into color object + * + * @param string $value + * @return AbstractColor + */ + public function parseColor($value) + { + return new Color($value); + } + + /** + * Checks if core module installation is available + * + * @return boolean + */ + protected function coreAvailable() + { + return (extension_loaded('gd') && function_exists('gd_info')); + } + + /** + * Returns clone of given core + * + * @return mixed + */ + public function cloneCore($core) + { + $width = imagesx($core); + $height = imagesy($core); + $clone = imagecreatetruecolor($width, $height); + imagealphablending($clone, false); + imagesavealpha($clone, true); + $transparency = imagecolorallocatealpha($clone, 0, 0, 0, 127); + imagefill($clone, 0, 0, $transparency); + + imagecopy($clone, $core, 0, 0, 0, 0, $width, $height); + + return $clone; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php b/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php new file mode 100644 index 0000000000..a3fd2f1276 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Encoder.php @@ -0,0 +1,122 @@ +image->getCore(), null, $this->quality); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_JPEG); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as PNG string + * + * @return string + */ + protected function processPng() + { + ob_start(); + $resource = $this->image->getCore(); + imagealphablending($resource, false); + imagesavealpha($resource, true); + imagepng($resource, null, -1); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_PNG); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as GIF string + * + * @return string + */ + protected function processGif() + { + ob_start(); + imagegif($this->image->getCore()); + $this->image->mime = image_type_to_mime_type(IMAGETYPE_GIF); + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + protected function processWebp() + { + if ( ! function_exists('imagewebp')) { + throw new \Intervention\Image\Exception\NotSupportedException( + "Webp format is not supported by PHP installation." + ); + } + + ob_start(); + imagewebp($this->image->getCore(), null, $this->quality); + $this->image->mime = defined('IMAGETYPE_WEBP') ? image_type_to_mime_type(IMAGETYPE_WEBP) : 'image/webp'; + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Processes and returns encoded image as TIFF string + * + * @return string + */ + protected function processTiff() + { + throw new \Intervention\Image\Exception\NotSupportedException( + "TIFF format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as BMP string + * + * @return string + */ + protected function processBmp() + { + throw new \Intervention\Image\Exception\NotSupportedException( + "BMP format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as ICO string + * + * @return string + */ + protected function processIco() + { + throw new \Intervention\Image\Exception\NotSupportedException( + "ICO format is not supported by Gd Driver." + ); + } + + /** + * Processes and returns encoded image as PSD string + * + * @return string + */ + protected function processPsd() + { + throw new \Intervention\Image\Exception\NotSupportedException( + "PSD format is not supported by Gd Driver." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Font.php b/vendor/intervention/image/src/Intervention/Image/Gd/Font.php new file mode 100644 index 0000000000..828c7af70a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Font.php @@ -0,0 +1,255 @@ +size * 0.75)); + } + + /** + * Filter function to access internal integer font values + * + * @return int + */ + private function getInternalFont() + { + $internalfont = is_null($this->file) ? 1 : $this->file; + $internalfont = is_numeric($internalfont) ? $internalfont : false; + + if ( ! in_array($internalfont, [1, 2, 3, 4, 5])) { + throw new \Intervention\Image\Exception\NotSupportedException( + sprintf('Internal GD font (%s) not available. Use only 1-5.', $internalfont) + ); + } + + return intval($internalfont); + } + + /** + * Get width of an internal font character + * + * @return int + */ + private function getInternalFontWidth() + { + return $this->getInternalFont() + 4; + } + + /** + * Get height of an internal font character + * + * @return int + */ + private function getInternalFontHeight() + { + switch ($this->getInternalFont()) { + case 1: + return 8; + + case 2: + return 14; + + case 3: + return 14; + + case 4: + return 16; + + case 5: + return 16; + } + } + + /** + * Calculates bounding box of current font setting + * + * @return Array + */ + public function getBoxSize() + { + $box = []; + + if ($this->hasApplicableFontFile()) { + + // get bounding box with angle 0 + $box = imagettfbbox($this->getPointSize(), 0, $this->file, $this->text); + + // rotate points manually + if ($this->angle != 0) { + + $angle = pi() * 2 - $this->angle * pi() * 2 / 360; + + for ($i=0; $i<4; $i++) { + $x = $box[$i * 2]; + $y = $box[$i * 2 + 1]; + $box[$i * 2] = cos($angle) * $x - sin($angle) * $y; + $box[$i * 2 + 1] = sin($angle) * $x + cos($angle) * $y; + } + } + + $box['width'] = intval(abs($box[4] - $box[0])); + $box['height'] = intval(abs($box[5] - $box[1])); + + } else { + + // get current internal font size + $width = $this->getInternalFontWidth(); + $height = $this->getInternalFontHeight(); + + if (strlen($this->text) == 0) { + // no text -> no boxsize + $box['width'] = 0; + $box['height'] = 0; + } else { + // calculate boxsize + $box['width'] = strlen($this->text) * $width; + $box['height'] = $height; + } + } + + return $box; + } + + /** + * Draws font to given image at given position + * + * @param Image $image + * @param int $posx + * @param int $posy + * @return void + */ + public function applyToImage(Image $image, $posx = 0, $posy = 0) + { + // parse text color + $color = new Color($this->color); + + if ($this->hasApplicableFontFile()) { + + if ($this->angle != 0 || is_string($this->align) || is_string($this->valign)) { + + $box = $this->getBoxSize(); + + $align = is_null($this->align) ? 'left' : strtolower($this->align); + $valign = is_null($this->valign) ? 'bottom' : strtolower($this->valign); + + // correction on position depending on v/h alignment + switch ($align.'-'.$valign) { + + case 'center-top': + $posx = $posx - round(($box[6]+$box[4])/2); + $posy = $posy - round(($box[7]+$box[5])/2); + break; + + case 'right-top': + $posx = $posx - $box[4]; + $posy = $posy - $box[5]; + break; + + case 'left-top': + $posx = $posx - $box[6]; + $posy = $posy - $box[7]; + break; + + case 'center-center': + case 'center-middle': + $posx = $posx - round(($box[0]+$box[4])/2); + $posy = $posy - round(($box[1]+$box[5])/2); + break; + + case 'right-center': + case 'right-middle': + $posx = $posx - round(($box[2]+$box[4])/2); + $posy = $posy - round(($box[3]+$box[5])/2); + break; + + case 'left-center': + case 'left-middle': + $posx = $posx - round(($box[0]+$box[6])/2); + $posy = $posy - round(($box[1]+$box[7])/2); + break; + + case 'center-bottom': + $posx = $posx - round(($box[0]+$box[2])/2); + $posy = $posy - round(($box[1]+$box[3])/2); + break; + + case 'right-bottom': + $posx = $posx - $box[2]; + $posy = $posy - $box[3]; + break; + + case 'left-bottom': + $posx = $posx - $box[0]; + $posy = $posy - $box[1]; + break; + } + } + + // enable alphablending for imagettftext + imagealphablending($image->getCore(), true); + + // draw ttf text + imagettftext($image->getCore(), $this->getPointSize(), $this->angle, $posx, $posy, $color->getInt(), $this->file, $this->text); + + } else { + + // get box size + $box = $this->getBoxSize(); + $width = $box['width']; + $height = $box['height']; + + // internal font specific position corrections + if ($this->getInternalFont() == 1) { + $top_correction = 1; + $bottom_correction = 2; + } elseif ($this->getInternalFont() == 3) { + $top_correction = 2; + $bottom_correction = 4; + } else { + $top_correction = 3; + $bottom_correction = 4; + } + + // x-position corrections for horizontal alignment + switch (strtolower($this->align)) { + case 'center': + $posx = ceil($posx - ($width / 2)); + break; + + case 'right': + $posx = ceil($posx - $width) + 1; + break; + } + + // y-position corrections for vertical alignment + switch (strtolower($this->valign)) { + case 'center': + case 'middle': + $posy = ceil($posy - ($height / 2)); + break; + + case 'top': + $posy = ceil($posy - $top_correction); + break; + + default: + case 'bottom': + $posy = round($posy - $height + $bottom_correction); + break; + } + + // draw text + imagestring($image->getCore(), $this->getInternalFont(), $posx, $posy, $this->text, $color->getInt()); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php new file mode 100644 index 0000000000..c3c42144d3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/CircleShape.php @@ -0,0 +1,40 @@ +width = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter; + } + + /** + * Draw current circle on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + return parent::applyToImage($image, $x, $y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php new file mode 100644 index 0000000000..c2e81c04ef --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/EllipseShape.php @@ -0,0 +1,64 @@ +width = is_numeric($width) ? intval($width) : $this->width; + $this->height = is_numeric($height) ? intval($height) : $this->height; + } + + /** + * Draw ellipse instance on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + // parse background color + $background = new Color($this->background); + + if ($this->hasBorder()) { + // slightly smaller ellipse to keep 1px bordered edges clean + imagefilledellipse($image->getCore(), $x, $y, $this->width-1, $this->height-1, $background->getInt()); + + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + + // gd's imageellipse doesn't respect imagesetthickness so i use imagearc with 359.9 degrees here + imagearc($image->getCore(), $x, $y, $this->width, $this->height, 0, 359.99, $border_color->getInt()); + } else { + imagefilledellipse($image->getCore(), $x, $y, $this->width, $this->height, $background->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php new file mode 100644 index 0000000000..c1eedd688a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/LineShape.php @@ -0,0 +1,89 @@ +x = is_numeric($x) ? intval($x) : $this->x; + $this->y = is_numeric($y) ? intval($y) : $this->y; + } + + /** + * Set current line color + * + * @param string $color + * @return void + */ + public function color($color) + { + $this->color = $color; + } + + /** + * Set current line width in pixels + * + * @param int $width + * @return void + */ + public function width($width) + { + throw new \Intervention\Image\Exception\NotSupportedException( + "Line width is not supported by GD driver." + ); + } + + /** + * Draw current instance of line to given endpoint on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $color = new Color($this->color); + imageline($image->getCore(), $x, $y, $this->x, $this->y, $color->getInt()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php new file mode 100644 index 0000000000..b11c6a11b2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/PolygonShape.php @@ -0,0 +1,48 @@ +points = $points; + } + + /** + * Draw polygon on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $background = new Color($this->background); + imagefilledpolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $background->getInt()); + + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + imagepolygon($image->getCore(), $this->points, intval(count($this->points) / 2), $border_color->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php new file mode 100644 index 0000000000..7244949634 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Gd/Shapes/RectangleShape.php @@ -0,0 +1,75 @@ +x1 = is_numeric($x1) ? intval($x1) : $this->x1; + $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1; + $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2; + $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2; + } + + /** + * Draw rectangle to given image at certain position + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $background = new Color($this->background); + imagefilledrectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $background->getInt()); + + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + imagesetthickness($image->getCore(), $this->border_width); + imagerectangle($image->getCore(), $this->x1, $this->y1, $this->x2, $this->y2, $border_color->getInt()); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Image.php b/vendor/intervention/image/src/Intervention/Image/Image.php new file mode 100644 index 0000000000..6c8dbf07a0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Image.php @@ -0,0 +1,363 @@ +driver = $driver; + $this->core = $core; + } + + /** + * Magic method to catch all image calls + * usually any AbstractCommand + * + * @param string $name + * @param Array $arguments + * @return mixed + */ + public function __call($name, $arguments) + { + $command = $this->driver->executeCommand($this, $name, $arguments); + return $command->hasOutput() ? $command->getOutput() : $this; + } + + /** + * Starts encoding of current image + * + * @param string $format + * @param int $quality + * @return \Intervention\Image\Image + */ + public function encode($format = null, $quality = 90) + { + return $this->driver->encode($this, $format, $quality); + } + + /** + * Saves encoded image in filesystem + * + * @param string $path + * @param int $quality + * @return \Intervention\Image\Image + */ + public function save($path = null, $quality = null) + { + $path = is_null($path) ? $this->basePath() : $path; + + if (is_null($path)) { + throw new Exception\NotWritableException( + "Can't write to undefined path." + ); + } + + $data = $this->encode(pathinfo($path, PATHINFO_EXTENSION), $quality); + $saved = @file_put_contents($path, $data); + + if ($saved === false) { + throw new Exception\NotWritableException( + "Can't write image data to path ({$path})" + ); + } + + // set new file info + $this->setFileInfoFromPath($path); + + return $this; + } + + /** + * Runs a given filter on current image + * + * @param FiltersFilterInterface $filter + * @return \Intervention\Image\Image + */ + public function filter(Filters\FilterInterface $filter) + { + return $filter->applyFilter($this); + } + + /** + * Returns current image driver + * + * @return \Intervention\Image\AbstractDriver + */ + public function getDriver() + { + return $this->driver; + } + + /** + * Sets current image driver + * @param AbstractDriver $driver + */ + public function setDriver(AbstractDriver $driver) + { + $this->driver = $driver; + + return $this; + } + + /** + * Returns current image resource/obj + * + * @return mixed + */ + public function getCore() + { + return $this->core; + } + + /** + * Sets current image resource + * + * @param mixed $core + */ + public function setCore($core) + { + $this->core = $core; + + return $this; + } + + /** + * Returns current image backup + * + * @param string $name + * @return mixed + */ + public function getBackup($name = null) + { + $name = is_null($name) ? 'default' : $name; + + if ( ! $this->backupExists($name)) { + throw new \Intervention\Image\Exception\RuntimeException( + "Backup with name ({$name}) not available. Call backup() before reset()." + ); + } + + return $this->backups[$name]; + } + + /** + * Returns all backups attached to image + * + * @return array + */ + public function getBackups() + { + return $this->backups; + } + + /** + * Sets current image backup + * + * @param mixed $resource + * @param string $name + * @return self + */ + public function setBackup($resource, $name = null) + { + $name = is_null($name) ? 'default' : $name; + + $this->backups[$name] = $resource; + + return $this; + } + + /** + * Checks if named backup exists + * + * @param string $name + * @return bool + */ + private function backupExists($name) + { + return array_key_exists($name, $this->backups); + } + + /** + * Checks if current image is already encoded + * + * @return boolean + */ + public function isEncoded() + { + return ! empty($this->encoded); + } + + /** + * Returns encoded image data of current image + * + * @return string + */ + public function getEncoded() + { + return $this->encoded; + } + + /** + * Sets encoded image buffer + * + * @param string $value + */ + public function setEncoded($value) + { + $this->encoded = $value; + + return $this; + } + + /** + * Calculates current image width + * + * @return int + */ + public function getWidth() + { + return $this->getSize()->width; + } + + /** + * Alias of getWidth() + * + * @return int + */ + public function width() + { + return $this->getWidth(); + } + + /** + * Calculates current image height + * + * @return int + */ + public function getHeight() + { + return $this->getSize()->height; + } + + /** + * Alias of getHeight + * + * @return int + */ + public function height() + { + return $this->getHeight(); + } + + /** + * Reads mime type + * + * @return string + */ + public function mime() + { + return $this->mime; + } + + /** + * Returns encoded image data in string conversion + * + * @return string + */ + public function __toString() + { + return $this->encoded; + } + + /** + * Cloning an image + */ + public function __clone() + { + $this->core = $this->driver->cloneCore($this->core); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageManager.php b/vendor/intervention/image/src/Intervention/Image/ImageManager.php new file mode 100644 index 0000000000..7c9f1c91f2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageManager.php @@ -0,0 +1,140 @@ + 'gd' + ]; + + /** + * Creates new instance of Image Manager + * + * @param array $config + */ + public function __construct(array $config = []) + { + $this->checkRequirements(); + $this->configure($config); + } + + /** + * Overrides configuration settings + * + * @param array $config + * + * @return self + */ + public function configure(array $config = []) + { + $this->config = array_replace($this->config, $config); + + return $this; + } + + /** + * Initiates an Image instance from different input types + * + * @param mixed $data + * + * @return \Intervention\Image\Image + */ + public function make($data) + { + return $this->createDriver()->init($data); + } + + /** + * Creates an empty image canvas + * + * @param int $width + * @param int $height + * @param mixed $background + * + * @return \Intervention\Image\Image + */ + public function canvas($width, $height, $background = null) + { + return $this->createDriver()->newImage($width, $height, $background); + } + + /** + * Create new cached image and run callback + * (requires additional package intervention/imagecache) + * + * @param Closure $callback + * @param int $lifetime + * @param boolean $returnObj + * + * @return Image + */ + public function cache(Closure $callback, $lifetime = null, $returnObj = false) + { + if (class_exists('Intervention\\Image\\ImageCache')) { + // create imagecache + $imagecache = new ImageCache($this); + + // run callback + if (is_callable($callback)) { + $callback($imagecache); + } + + return $imagecache->get($lifetime, $returnObj); + } + + throw new \Intervention\Image\Exception\MissingDependencyException( + "Please install package intervention/imagecache before running this function." + ); + } + + /** + * Creates a driver instance according to config settings + * + * @return \Intervention\Image\AbstractDriver + */ + private function createDriver() + { + if (is_string($this->config['driver'])) { + $drivername = ucfirst($this->config['driver']); + $driverclass = sprintf('Intervention\\Image\\%s\\Driver', $drivername); + + if (class_exists($driverclass)) { + return new $driverclass; + } + + throw new \Intervention\Image\Exception\NotSupportedException( + "Driver ({$drivername}) could not be instantiated." + ); + } + + if ($this->config['driver'] instanceof AbstractDriver) { + return $this->config['driver']; + } + + throw new \Intervention\Image\Exception\NotSupportedException( + "Unknown driver type." + ); + } + + /** + * Check if all requirements are available + * + * @return void + */ + private function checkRequirements() + { + if ( ! function_exists('finfo_buffer')) { + throw new \Intervention\Image\Exception\MissingDependencyException( + "PHP Fileinfo extension must be installed/enabled to use Intervention Image." + ); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php b/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php new file mode 100644 index 0000000000..ad549c8ca9 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageManagerStatic.php @@ -0,0 +1,87 @@ +configure($config); + } + + /** + * Statically initiates an Image instance from different input types + * + * @param mixed $data + * + * @return \Intervention\Image\Image + */ + public static function make($data) + { + return self::getManager()->make($data); + } + + /** + * Statically creates an empty image canvas + * + * @param int $width + * @param int $height + * @param mixed $background + * + * @return \Intervention\Image\Image + */ + public static function canvas($width, $height, $background = null) + { + return self::getManager()->canvas($width, $height, $background); + } + + /** + * Create new cached image and run callback statically + * + * @param Closure $callback + * @param int $lifetime + * @param boolean $returnObj + * + * @return mixed + */ + public static function cache(Closure $callback, $lifetime = null, $returnObj = false) + { + return self::getManager()->cache($callback, $lifetime, $returnObj); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php new file mode 100644 index 0000000000..1e6351df78 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProvider.php @@ -0,0 +1,85 @@ +provider = $this->getProvider(); + } + + /** + * Bootstrap the application events. + * + * @return void + */ + public function boot() + { + if (method_exists($this->provider, 'boot')) { + return $this->provider->boot(); + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + return $this->provider->register(); + } + + /** + * Return ServiceProvider according to Laravel version + * + * @return \Intervention\Image\Provider\ProviderInterface + */ + private function getProvider() + { + if ($this->app instanceof \Laravel\Lumen\Application) { + $provider = '\Intervention\Image\ImageServiceProviderLumen'; + } elseif (version_compare(\Illuminate\Foundation\Application::VERSION, '5.0', '<')) { + $provider = '\Intervention\Image\ImageServiceProviderLaravel4'; + } else { + $provider = '\Intervention\Image\ImageServiceProviderLaravel5'; + } + + return new $provider($this->app); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['image']; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php new file mode 100755 index 0000000000..3b1388f252 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel4.php @@ -0,0 +1,112 @@ +package('intervention/image'); + + // try to create imagecache route only if imagecache is present + if (class_exists('Intervention\\Image\\ImageCache')) { + + $app = $this->app; + + // load imagecache config + $app['config']->package('intervention/imagecache', __DIR__.'/../../../../imagecache/src/config', 'imagecache'); + $config = $app['config']; + + // create dynamic manipulation route + if (is_string($config->get('imagecache::route'))) { + + // add original to route templates + $config->set('imagecache::templates.original', null); + + // setup image manipulator route + $app['router']->get($config->get('imagecache::route').'/{template}/{filename}', ['as' => 'imagecache', function ($template, $filename) use ($app, $config) { + + // disable session cookies for image route + $app['config']->set('session.driver', 'array'); + + // find file + foreach ($config->get('imagecache::paths') as $path) { + // don't allow '..' in filenames + $image_path = $path.'/'.str_replace('..', '', $filename); + if (file_exists($image_path) && is_file($image_path)) { + break; + } else { + $image_path = false; + } + } + + // abort if file not found + if ($image_path === false) { + $app->abort(404); + } + + // define template callback + $callback = $config->get("imagecache::templates.{$template}"); + + if (is_callable($callback) || class_exists($callback)) { + + // image manipulation based on callback + $content = $app['image']->cache(function ($image) use ($image_path, $callback) { + + switch (true) { + case is_callable($callback): + return $callback($image->make($image_path)); + break; + + case class_exists($callback): + return $image->make($image_path)->filter(new $callback); + break; + } + + }, $config->get('imagecache::lifetime')); + + } else { + + // get original image file contents + $content = file_get_contents($image_path); + } + + // define mime type + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content); + + // return http response + return new IlluminateResponse($content, 200, [ + 'Content-Type' => $mime, + 'Cache-Control' => 'max-age='.($config->get('imagecache::lifetime')*60).', public', + 'Etag' => md5($content) + ]); + + }])->where(['template' => join('|', array_keys($config->get('imagecache::templates'))), 'filename' => '[ \w\\.\\/\\-]+']); + } + } + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $app = $this->app; + + $app['image'] = $app->share(function ($app) { + return new ImageManager($app['config']->get('image::config')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel5.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel5.php new file mode 100644 index 0000000000..ea04fec5c6 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLaravel5.php @@ -0,0 +1,89 @@ +publishes([ + __DIR__.'/../../config/config.php' => config_path('image.php') + ]); + + // setup intervention/imagecache if package is installed + $this->cacheIsInstalled() ? $this->bootstrapImageCache() : null; + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $app = $this->app; + + // merge default config + $this->mergeConfigFrom( + __DIR__.'/../../config/config.php', + 'image' + ); + + // create image + $app->singleton('image', function ($app) { + return new ImageManager($app['config']->get('image')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } + + /** + * Bootstrap imagecache + * + * @return void + */ + private function bootstrapImageCache() + { + $app = $this->app; + $config = __DIR__.'/../../../../imagecache/src/config/config.php'; + + $this->publishes([ + $config => config_path('imagecache.php') + ]); + + // merge default config + $this->mergeConfigFrom( + $config, + 'imagecache' + ); + + // imagecache route + if (is_string(config('imagecache.route'))) { + + $filename_pattern = '[ \w\\.\\/\\-\\@\(\)]+'; + + // route to access template applied image file + $app['router']->get(config('imagecache.route').'/{template}/{filename}', [ + 'uses' => 'Intervention\Image\ImageCacheController@getResponse', + 'as' => 'imagecache' + ])->where(['filename' => $filename_pattern]); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php new file mode 100644 index 0000000000..b756a61f5e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLeague.php @@ -0,0 +1,42 @@ +config = $config; + } + + /** + * Register the server provider. + * + * @return void + */ + public function register() + { + $this->getContainer()->share('Intervention\Image\ImageManager', function () { + return new ImageManager($this->config); + }); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php new file mode 100644 index 0000000000..4a381ccd41 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/ImageServiceProviderLumen.php @@ -0,0 +1,34 @@ +app; + + // merge default config + $this->mergeConfigFrom( + __DIR__.'/../../config/config.php', + 'image' + ); + + // set configuration + $app->configure('image'); + + // create image + $app->singleton('image',function ($app) { + return new ImageManager($app['config']->get('image')); + }); + + $app->alias('image', 'Intervention\Image\ImageManager'); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php new file mode 100644 index 0000000000..91db8217cf --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Color.php @@ -0,0 +1,277 @@ +> 24) & 0xFF; + $r = ($value >> 16) & 0xFF; + $g = ($value >> 8) & 0xFF; + $b = $value & 0xFF; + $a = $this->rgb2alpha($a); + + $this->setPixel($r, $g, $b, $a); + } + + /** + * Initiates color object from given array + * + * @param array $value + * @return \Intervention\Image\AbstractColor + */ + public function initFromArray($array) + { + $array = array_values($array); + + if (count($array) == 4) { + + // color array with alpha value + list($r, $g, $b, $a) = $array; + + } elseif (count($array) == 3) { + + // color array without alpha value + list($r, $g, $b) = $array; + $a = 1; + } + + $this->setPixel($r, $g, $b, $a); + } + + /** + * Initiates color object from given string + * + * @param string $value + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromString($value) + { + if ($color = $this->rgbaFromString($value)) { + $this->setPixel($color[0], $color[1], $color[2], $color[3]); + } + } + + /** + * Initiates color object from given ImagickPixel object + * + * @param ImagickPixel $value + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromObject($value) + { + if (is_a($value, '\ImagickPixel')) { + $this->pixel = $value; + } + } + + /** + * Initiates color object from given R, G and B values + * + * @param int $r + * @param int $g + * @param int $b + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgb($r, $g, $b) + { + $this->setPixel($r, $g, $b); + } + + /** + * Initiates color object from given R, G, B and A values + * + * @param int $r + * @param int $g + * @param int $b + * @param float $a + * + * @return \Intervention\Image\AbstractColor + */ + public function initFromRgba($r, $g, $b, $a) + { + $this->setPixel($r, $g, $b, $a); + } + + /** + * Calculates integer value of current color instance + * + * @return int + */ + public function getInt() + { + $r = $this->getRedValue(); + $g = $this->getGreenValue(); + $b = $this->getBlueValue(); + $a = intval(round($this->getAlphaValue() * 255)); + + return intval(($a << 24) + ($r << 16) + ($g << 8) + $b); + } + + /** + * Calculates hexadecimal value of current color instance + * + * @param string $prefix + * + * @return string + */ + public function getHex($prefix = '') + { + return sprintf('%s%02x%02x%02x', $prefix, + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue() + ); + } + + /** + * Calculates RGB(A) in array format of current color instance + * + * @return array + */ + public function getArray() + { + return [ + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue(), + $this->getAlphaValue() + ]; + } + + /** + * Calculates RGBA in string format of current color instance + * + * @return string + */ + public function getRgba() + { + return sprintf('rgba(%d, %d, %d, %.2F)', + $this->getRedValue(), + $this->getGreenValue(), + $this->getBlueValue(), + $this->getAlphaValue() + ); + } + + /** + * Determines if current color is different from given color + * + * @param AbstractColor $color + * @param int $tolerance + * @return boolean + */ + public function differs(\Intervention\Image\AbstractColor $color, $tolerance = 0) + { + $color_tolerance = round($tolerance * 2.55); + $alpha_tolerance = round($tolerance); + + $delta = [ + 'r' => abs($color->getRedValue() - $this->getRedValue()), + 'g' => abs($color->getGreenValue() - $this->getGreenValue()), + 'b' => abs($color->getBlueValue() - $this->getBlueValue()), + 'a' => abs($color->getAlphaValue() - $this->getAlphaValue()) + ]; + + return ( + $delta['r'] > $color_tolerance or + $delta['g'] > $color_tolerance or + $delta['b'] > $color_tolerance or + $delta['a'] > $alpha_tolerance + ); + } + + /** + * Returns RGB red value of current color + * + * @return int + */ + public function getRedValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_RED) * 255)); + } + + /** + * Returns RGB green value of current color + * + * @return int + */ + public function getGreenValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_GREEN) * 255)); + } + + /** + * Returns RGB blue value of current color + * + * @return int + */ + public function getBlueValue() + { + return intval(round($this->pixel->getColorValue(\Imagick::COLOR_BLUE) * 255)); + } + + /** + * Returns RGB alpha value of current color + * + * @return float + */ + public function getAlphaValue() + { + return round($this->pixel->getColorValue(\Imagick::COLOR_ALPHA), 2); + } + + /** + * Initiates ImagickPixel from given RGBA values + * + * @return \ImagickPixel + */ + private function setPixel($r, $g, $b, $a = null) + { + $a = is_null($a) ? 1 : $a; + + return $this->pixel = new \ImagickPixel( + sprintf('rgba(%d, %d, %d, %.2F)', $r, $g, $b, $a) + ); + } + + /** + * Returns current color as ImagickPixel + * + * @return \ImagickPixel + */ + public function getPixel() + { + return $this->pixel; + } + + /** + * Calculates RGA integer alpha value into float value + * + * @param int $value + * @return float + */ + private function rgb2alpha($value) + { + // (255 -> 1.0) / (0 -> 0.0) + return (float) round($value/255, 2); + } + +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php new file mode 100644 index 0000000000..60dedb2b7e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BackupCommand.php @@ -0,0 +1,23 @@ +argument(0)->value(); + + // clone current image resource + $clone = clone $image; + $image->setBackup($clone->getCore(), $backupName); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php new file mode 100644 index 0000000000..b037c15162 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BlurCommand.php @@ -0,0 +1,19 @@ +argument(0)->between(0, 100)->value(1); + + return $image->getCore()->blurImage(1 * $amount, 0.5 * $amount); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php new file mode 100644 index 0000000000..eefb1802cc --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/BrightnessCommand.php @@ -0,0 +1,19 @@ +argument(0)->between(-100, 100)->required()->value(); + + return $image->getCore()->modulateImage(100 + $level, 100, 100); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php new file mode 100644 index 0000000000..51142be275 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ColorizeCommand.php @@ -0,0 +1,42 @@ +argument(0)->between(-100, 100)->required()->value(); + $green = $this->argument(1)->between(-100, 100)->required()->value(); + $blue = $this->argument(2)->between(-100, 100)->required()->value(); + + // normalize colorize levels + $red = $this->normalizeLevel($red); + $green = $this->normalizeLevel($green); + $blue = $this->normalizeLevel($blue); + + $qrange = $image->getCore()->getQuantumRange(); + + // apply + $image->getCore()->levelImage(0, $red, $qrange['quantumRangeLong'], \Imagick::CHANNEL_RED); + $image->getCore()->levelImage(0, $green, $qrange['quantumRangeLong'], \Imagick::CHANNEL_GREEN); + $image->getCore()->levelImage(0, $blue, $qrange['quantumRangeLong'], \Imagick::CHANNEL_BLUE); + + return true; + } + + private function normalizeLevel($level) + { + if ($level > 0) { + return $level/5; + } else { + return ($level+100)/100; + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php new file mode 100644 index 0000000000..113a2186c1 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ContrastCommand.php @@ -0,0 +1,19 @@ +argument(0)->between(-100, 100)->required()->value(); + + return $image->getCore()->sigmoidalContrastImage($level > 0, $level / 4, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php new file mode 100644 index 0000000000..21c7184b71 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/CropCommand.php @@ -0,0 +1,43 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $x = $this->argument(2)->type('digit')->value(); + $y = $this->argument(3)->type('digit')->value(); + + if (is_null($width) || is_null($height)) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "Width and height of cutout needs to be defined." + ); + } + + $cropped = new Size($width, $height); + $position = new Point($x, $y); + + // align boxes + if (is_null($x) && is_null($y)) { + $position = $image->getSize()->align('center')->relativePosition($cropped->align('center')); + } + + // crop image core + $image->getCore()->cropImage($cropped->width, $cropped->height, $position->x, $position->y); + $image->getCore()->setImagePage(0,0,0,0); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php new file mode 100644 index 0000000000..d98062d8a3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/DestroyCommand.php @@ -0,0 +1,25 @@ +getCore()->clear(); + + // destroy backups + foreach ($image->getBackups() as $backup) { + $backup->clear(); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php new file mode 100644 index 0000000000..924522caff --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ExifCommand.php @@ -0,0 +1,62 @@ +preferExtension = false; + } + + /** + * Read Exif data from the given image + * + * @param \Intervention\Image\Image $image + * @return boolean + */ + public function execute($image) + { + if ($this->preferExtension && function_exists('exif_read_data')) { + return parent::execute($image); + } + + $core = $image->getCore(); + + if ( ! method_exists($core, 'getImageProperties')) { + throw new \Intervention\Image\Exception\NotSupportedException( + "Reading Exif data is not supported by this PHP installation." + ); + } + + $requestedKey = $this->argument(0)->value(); + if ($requestedKey !== null) { + $this->setOutput($core->getImageProperty('exif:' . $requestedKey)); + return true; + } + + $exif = []; + $properties = $core->getImageProperties(); + foreach ($properties as $key => $value) { + if (substr($key, 0, 5) !== 'exif:') { + continue; + } + + $exif[substr($key, 5)] = $value; + } + + $this->setOutput($exif); + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php new file mode 100644 index 0000000000..bfac75fbff --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FillCommand.php @@ -0,0 +1,103 @@ +argument(0)->value(); + $x = $this->argument(1)->type('digit')->value(); + $y = $this->argument(2)->type('digit')->value(); + + $imagick = $image->getCore(); + + try { + // set image filling + $source = new Decoder; + $filling = $source->init($filling); + + } catch (\Intervention\Image\Exception\NotReadableException $e) { + + // set solid color filling + $filling = new Color($filling); + } + + // flood fill if coordinates are set + if (is_int($x) && is_int($y)) { + + // flood fill with texture + if ($filling instanceof Image) { + + // create tile + $tile = clone $image->getCore(); + + // mask away color at position + $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false); + + // create canvas + $canvas = clone $image->getCore(); + + // fill canvas with texture + $canvas = $canvas->textureImage($filling->getCore()); + + // merge canvas and tile + $canvas->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // replace image core + $image->setCore($canvas); + + // flood fill with color + } elseif ($filling instanceof Color) { + + // create canvas with filling + $canvas = new \Imagick; + $canvas->newImage($image->getWidth(), $image->getHeight(), $filling->getPixel(), 'png'); + + // create tile to put on top + $tile = clone $image->getCore(); + + // mask away color at pos. + $tile->transparentPaintImage($tile->getImagePixelColor($x, $y), 0, 0, false); + + // save alpha channel of original image + $alpha = clone $image->getCore(); + + // merge original with canvas and tile + $image->getCore()->compositeImage($canvas, \Imagick::COMPOSITE_DEFAULT, 0, 0); + $image->getCore()->compositeImage($tile, \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // restore alpha channel of original image + $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + } + + } else { + + if ($filling instanceof Image) { + + // fill whole image with texture + $image->setCore($image->getCore()->textureImage($filling->getCore())); + + } elseif ($filling instanceof Color) { + + // fill whole image with color + $draw = new \ImagickDraw(); + $draw->setFillColor($filling->getPixel()); + $draw->rectangle(0, 0, $image->getWidth(), $image->getHeight()); + $image->getCore()->drawImage($draw); + } + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php new file mode 100644 index 0000000000..f2c60d2193 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FitCommand.php @@ -0,0 +1,41 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->value($width); + $constraints = $this->argument(2)->type('closure')->value(); + $position = $this->argument(3)->type('string')->value('center'); + + // calculate size + $cropped = $image->getSize()->fit(new Size($width, $height), $position); + $resized = clone $cropped; + $resized = $resized->resize($width, $height, $constraints); + + // crop image + $image->getCore()->cropImage( + $cropped->width, + $cropped->height, + $cropped->pivot->x, + $cropped->pivot->y + ); + + // resize image + $image->getCore()->scaleImage($resized->getWidth(), $resized->getHeight()); + $image->getCore()->setImagePage(0,0,0,0); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php new file mode 100644 index 0000000000..cdb03c52de --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/FlipCommand.php @@ -0,0 +1,25 @@ +argument(0)->value('h'); + + if (in_array(strtolower($mode), [2, 'v', 'vert', 'vertical'])) { + // flip vertical + return $image->getCore()->flipImage(); + } else { + // flip horizontal + return $image->getCore()->flopImage(); + } + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php new file mode 100644 index 0000000000..e70cbdd367 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GammaCommand.php @@ -0,0 +1,19 @@ +argument(0)->type('numeric')->required()->value(); + + return $image->getCore()->gammaImage($gamma); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php new file mode 100644 index 0000000000..65b1078d1b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GetSizeCommand.php @@ -0,0 +1,27 @@ +getCore(); + + $this->setOutput(new Size( + $core->getImageWidth(), + $core->getImageHeight() + )); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php new file mode 100644 index 0000000000..bb3f47260f --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/GreyscaleCommand.php @@ -0,0 +1,17 @@ +getCore()->modulateImage(100, 0, 100); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php new file mode 100644 index 0000000000..0b61e50c16 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/HeightenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = null; + $this->arguments[1] = $height; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php new file mode 100644 index 0000000000..542feb2ae7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InsertCommand.php @@ -0,0 +1,31 @@ +argument(0)->required()->value(); + $position = $this->argument(1)->type('string')->value(); + $x = $this->argument(2)->type('digit')->value(0); + $y = $this->argument(3)->type('digit')->value(0); + + // build watermark + $watermark = $image->getDriver()->init($source); + + // define insertion point + $image_size = $image->getSize()->align($position, $x, $y); + $watermark_size = $watermark->getSize()->align($position); + $target = $image_size->relativePosition($watermark_size); + + // insert image at position + return $image->getCore()->compositeImage($watermark->getCore(), \Imagick::COMPOSITE_DEFAULT, $target->x, $target->y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php new file mode 100644 index 0000000000..82cddd4c63 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InterlaceCommand.php @@ -0,0 +1,27 @@ +argument(0)->type('bool')->value(true); + + if ($mode) { + $mode = \Imagick::INTERLACE_LINE; + } else { + $mode = \Imagick::INTERLACE_NO; + } + + $image->getCore()->setInterlaceScheme($mode); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php new file mode 100644 index 0000000000..125fbddee0 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/InvertCommand.php @@ -0,0 +1,17 @@ +getCore()->negateImage(false); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php new file mode 100644 index 0000000000..7308180f66 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/LimitColorsCommand.php @@ -0,0 +1,57 @@ +argument(0)->value(); + $matte = $this->argument(1)->value(); + + // get current image size + $size = $image->getSize(); + + // build 2 color alpha mask from original alpha + $alpha = clone $image->getCore(); + $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + $alpha->transparentPaintImage('#ffffff', 0, 0, false); + $alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + $alpha->negateImage(false); + + if ($matte) { + + // get matte color + $mattecolor = $image->getDriver()->parseColor($matte)->getPixel(); + + // create matte image + $canvas = new \Imagick; + $canvas->newImage($size->width, $size->height, $mattecolor, 'png'); + + // lower colors of original and copy to matte + $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false); + $canvas->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0); + + // copy new alpha to canvas + $canvas->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + // replace core + $image->setCore($canvas); + + } else { + + $image->getCore()->quantizeImage($count, \Imagick::COLORSPACE_RGB, 0, false, false); + $image->getCore()->compositeImage($alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + } + + return true; + + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php new file mode 100644 index 0000000000..2dfc697b08 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/MaskCommand.php @@ -0,0 +1,58 @@ +argument(0)->value(); + $mask_w_alpha = $this->argument(1)->type('bool')->value(false); + + // get imagick + $imagick = $image->getCore(); + + // build mask image from source + $mask = $image->getDriver()->init($mask_source); + + // resize mask to size of current image (if necessary) + $image_size = $image->getSize(); + if ($mask->getSize() != $image_size) { + $mask->resize($image_size->width, $image_size->height); + } + + $imagick->setImageMatte(true); + + if ($mask_w_alpha) { + + // just mask with alpha map + $imagick->compositeImage($mask->getCore(), \Imagick::COMPOSITE_DSTIN, 0, 0); + + } else { + + // get alpha channel of original as greyscale image + $original_alpha = clone $imagick; + $original_alpha->separateImageChannel(\Imagick::CHANNEL_ALPHA); + + // use red channel from mask ask alpha + $mask_alpha = clone $mask->getCore(); + $mask_alpha->compositeImage($mask->getCore(), \Imagick::COMPOSITE_DEFAULT, 0, 0); + // $mask_alpha->setImageAlphaChannel(\Imagick::ALPHACHANNEL_DEACTIVATE); + $mask_alpha->separateImageChannel(\Imagick::CHANNEL_ALL); + + // combine both alphas from original and mask + $original_alpha->compositeImage($mask_alpha, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); + + // mask the image with the alpha combination + $imagick->compositeImage($original_alpha, \Imagick::COMPOSITE_DSTIN, 0, 0); + } + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php new file mode 100644 index 0000000000..57ed006b87 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/OpacityCommand.php @@ -0,0 +1,21 @@ +argument(0)->between(0, 100)->required()->value(); + + $transparency = $transparency > 0 ? (100 / $transparency) : 1000; + + return $image->getCore()->evaluateImage(\Imagick::EVALUATE_DIVIDE, $transparency, \Imagick::CHANNEL_ALPHA); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php new file mode 100644 index 0000000000..8daa0f95a2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PickColorCommand.php @@ -0,0 +1,29 @@ +argument(0)->type('digit')->required()->value(); + $y = $this->argument(1)->type('digit')->required()->value(); + $format = $this->argument(2)->type('string')->value('array'); + + // pick color + $color = new Color($image->getCore()->getImagePixelColor($x, $y)); + + // format to output + $this->setOutput($color->format($format)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php new file mode 100644 index 0000000000..b9e6d395d3 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelCommand.php @@ -0,0 +1,30 @@ +argument(0)->required()->value(); + $color = new Color($color); + $x = $this->argument(1)->type('digit')->required()->value(); + $y = $this->argument(2)->type('digit')->required()->value(); + + // prepare pixel + $draw = new \ImagickDraw; + $draw->setFillColor($color->getPixel()); + $draw->point($x, $y); + + // apply pixel + return $image->getCore()->drawImage($draw); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php new file mode 100644 index 0000000000..75f2218f58 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/PixelateCommand.php @@ -0,0 +1,25 @@ +argument(0)->type('digit')->value(10); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + $image->getCore()->scaleImage(max(1, ($width / $size)), max(1, ($height / $size))); + $image->getCore()->scaleImage($width, $height); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php new file mode 100644 index 0000000000..ee5a2cdf33 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResetCommand.php @@ -0,0 +1,37 @@ +argument(0)->value(); + + $backup = $image->getBackup($backupName); + + if ($backup instanceof \Imagick) { + + // destroy current core + $image->getCore()->clear(); + + // clone backup + $backup = clone $backup; + + // reset to new resource + $image->setCore($backup); + + return true; + } + + throw new \Intervention\Image\Exception\RuntimeException( + "Backup not available. Call backup({$backupName}) before reset()." + ); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php new file mode 100644 index 0000000000..f394c15dcc --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCanvasCommand.php @@ -0,0 +1,89 @@ +argument(0)->type('digit')->required()->value(); + $height = $this->argument(1)->type('digit')->required()->value(); + $anchor = $this->argument(2)->value('center'); + $relative = $this->argument(3)->type('boolean')->value(false); + $bgcolor = $this->argument(4)->value(); + + $original_width = $image->getWidth(); + $original_height = $image->getHeight(); + + // check of only width or height is set + $width = is_null($width) ? $original_width : intval($width); + $height = is_null($height) ? $original_height : intval($height); + + // check on relative width/height + if ($relative) { + $width = $original_width + $width; + $height = $original_height + $height; + } + + // check for negative width/height + $width = ($width <= 0) ? $width + $original_width : $width; + $height = ($height <= 0) ? $height + $original_height : $height; + + // create new canvas + $canvas = $image->getDriver()->newImage($width, $height, $bgcolor); + + // set copy position + $canvas_size = $canvas->getSize()->align($anchor); + $image_size = $image->getSize()->align($anchor); + $canvas_pos = $image_size->relativePosition($canvas_size); + $image_pos = $canvas_size->relativePosition($image_size); + + if ($width <= $original_width) { + $dst_x = 0; + $src_x = $canvas_pos->x; + $src_w = $canvas_size->width; + } else { + $dst_x = $image_pos->x; + $src_x = 0; + $src_w = $original_width; + } + + if ($height <= $original_height) { + $dst_y = 0; + $src_y = $canvas_pos->y; + $src_h = $canvas_size->height; + } else { + $dst_y = $image_pos->y; + $src_y = 0; + $src_h = $original_height; + } + + // make image area transparent to keep transparency + // even if background-color is set + $rect = new \ImagickDraw; + $fill = $canvas->pickColor(0, 0, 'hex'); + $fill = $fill == '#ff0000' ? '#00ff00' : '#ff0000'; + $rect->setFillColor($fill); + $rect->rectangle($dst_x, $dst_y, $dst_x + $src_w - 1, $dst_y + $src_h - 1); + $canvas->getCore()->drawImage($rect); + $canvas->getCore()->transparentPaintImage($fill, 0, 0, false); + + $canvas->getCore()->setImageColorspace($image->getCore()->getImageColorspace()); + + // copy image into new canvas + $image->getCore()->cropImage($src_w, $src_h, $src_x, $src_y); + $canvas->getCore()->compositeImage($image->getCore(), \Imagick::COMPOSITE_DEFAULT, $dst_x, $dst_y); + $canvas->getCore()->setImagePage(0,0,0,0); + + // set new core to canvas + $image->setCore($canvas->getCore()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php new file mode 100644 index 0000000000..9ccc202c2a --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/ResizeCommand.php @@ -0,0 +1,27 @@ +argument(0)->value(); + $height = $this->argument(1)->value(); + $constraints = $this->argument(2)->type('closure')->value(); + + // resize box + $resized = $image->getSize()->resize($width, $height, $constraints); + + // modify image + $image->getCore()->scaleImage($resized->getWidth(), $resized->getHeight()); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php new file mode 100644 index 0000000000..c4503a6ca5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/RotateCommand.php @@ -0,0 +1,29 @@ +argument(0)->type('numeric')->required()->value(); + $color = $this->argument(1)->value(); + $color = new Color($color); + + // restrict rotations beyond 360 degrees, since the end result is the same + $angle %= 360; + + // rotate image + $image->getCore()->rotateImage($color->getPixel(), ($angle * -1)); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php new file mode 100644 index 0000000000..4f2fc8c29d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/SharpenCommand.php @@ -0,0 +1,19 @@ +argument(0)->between(0, 100)->value(10); + + return $image->getCore()->unsharpMaskImage(1, 1, $amount / 6.25, 0); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php new file mode 100644 index 0000000000..f09590320e --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/TrimCommand.php @@ -0,0 +1,120 @@ +argument(0)->type('string')->value(); + $away = $this->argument(1)->value(); + $tolerance = $this->argument(2)->type('numeric')->value(0); + $feather = $this->argument(3)->type('numeric')->value(0); + + $width = $image->getWidth(); + $height = $image->getHeight(); + + $checkTransparency = false; + + // define borders to trim away + if (is_null($away)) { + $away = ['top', 'right', 'bottom', 'left']; + } elseif (is_string($away)) { + $away = [$away]; + } + + // lower border names + foreach ($away as $key => $value) { + $away[$key] = strtolower($value); + } + + // define base color position + switch (strtolower($base)) { + case 'transparent': + case 'trans': + $checkTransparency = true; + $base_x = 0; + $base_y = 0; + break; + + case 'bottom-right': + case 'right-bottom': + $base_x = $width - 1; + $base_y = $height - 1; + break; + + default: + case 'top-left': + case 'left-top': + $base_x = 0; + $base_y = 0; + break; + } + + // pick base color + if ($checkTransparency) { + $base_color = new Color; // color will only be used to compare alpha channel + } else { + $base_color = $image->pickColor($base_x, $base_y, 'object'); + } + + // trim on clone to get only coordinates + $trimed = clone $image->getCore(); + + // add border to trim specific color + $trimed->borderImage($base_color->getPixel(), 1, 1); + + // trim image + $trimed->trimImage(65850 / 100 * $tolerance); + + // get coordinates of trim + $imagePage = $trimed->getImagePage(); + list($crop_x, $crop_y) = [$imagePage['x']-1, $imagePage['y']-1]; + // $trimed->setImagePage(0, 0, 0, 0); + list($crop_width, $crop_height) = [$trimed->width, $trimed->height]; + + // adjust settings if right should not be trimed + if ( ! in_array('right', $away)) { + $crop_width = $crop_width + ($width - ($width - $crop_x)); + } + + // adjust settings if bottom should not be trimed + if ( ! in_array('bottom', $away)) { + $crop_height = $crop_height + ($height - ($height - $crop_y)); + } + + // adjust settings if left should not be trimed + if ( ! in_array('left', $away)) { + $crop_width = $crop_width + $crop_x; + $crop_x = 0; + } + + // adjust settings if top should not be trimed + if ( ! in_array('top', $away)) { + $crop_height = $crop_height + $crop_y; + $crop_y = 0; + } + + // add feather + $crop_width = min($width, ($crop_width + $feather * 2)); + $crop_height = min($height, ($crop_height + $feather * 2)); + $crop_x = max(0, ($crop_x - $feather)); + $crop_y = max(0, ($crop_y - $feather)); + + // finally crop based on page + $image->getCore()->cropImage($crop_width, $crop_height, $crop_x, $crop_y); + $image->getCore()->setImagePage(0,0,0,0); + + $trimed->destroy(); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php new file mode 100644 index 0000000000..a1967534ca --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Commands/WidenCommand.php @@ -0,0 +1,28 @@ +argument(0)->type('digit')->required()->value(); + $additionalConstraints = $this->argument(1)->type('closure')->value(); + + $this->arguments[0] = $width; + $this->arguments[1] = null; + $this->arguments[2] = function ($constraint) use ($additionalConstraints) { + $constraint->aspectRatio(); + if(is_callable($additionalConstraints)) + $additionalConstraints($constraint); + }; + + return parent::execute($image); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php new file mode 100644 index 0000000000..8cf2420ae5 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Decoder.php @@ -0,0 +1,121 @@ +setBackgroundColor(new \ImagickPixel('transparent')); + $core->readImage($path); + $core->setImageType(defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : \Imagick::IMGTYPE_TRUECOLORMATTE); + + } catch (\ImagickException $e) { + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to read image from path ({$path}).", + 0, + $e + ); + } + + // build image + $image = $this->initFromImagick($core); + $image->setFileInfoFromPath($path); + + return $image; + } + + /** + * Initiates new image from GD resource + * + * @param Resource $resource + * @return \Intervention\Image\Image + */ + public function initFromGdResource($resource) + { + throw new \Intervention\Image\Exception\NotSupportedException( + 'Imagick driver is unable to init from GD resource.' + ); + } + + /** + * Initiates new image from Imagick object + * + * @param Imagick $object + * @return \Intervention\Image\Image + */ + public function initFromImagick(\Imagick $object) + { + // currently animations are not supported + // so all images are turned into static + $object = $this->removeAnimation($object); + + // reset image orientation + $object->setImageOrientation(\Imagick::ORIENTATION_UNDEFINED); + + return new Image(new Driver, $object); + } + + /** + * Initiates new image from binary data + * + * @param string $data + * @return \Intervention\Image\Image + */ + public function initFromBinary($binary) + { + $core = new \Imagick; + + try { + $core->setBackgroundColor(new \ImagickPixel('transparent')); + + $core->readImageBlob($binary); + + } catch (\ImagickException $e) { + throw new \Intervention\Image\Exception\NotReadableException( + "Unable to read image from binary data.", + 0, + $e + ); + } + + // build image + $image = $this->initFromImagick($core); + $image->mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $binary); + + return $image; + } + + /** + * Turns object into one frame Imagick object + * by removing all frames except first + * + * @param Imagick $object + * @return Imagick + */ + private function removeAnimation(\Imagick $object) + { + $imagick = new \Imagick; + + foreach ($object as $frame) { + $imagick->addImage($frame->getImage()); + break; + } + + $object->destroy(); + + return $imagick; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php new file mode 100644 index 0000000000..1f2fcc6c13 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Driver.php @@ -0,0 +1,70 @@ +coreAvailable()) { + throw new \Intervention\Image\Exception\NotSupportedException( + "ImageMagick module not available with this PHP installation." + ); + } + + $this->decoder = $decoder ? $decoder : new Decoder; + $this->encoder = $encoder ? $encoder : new Encoder; + } + + /** + * Creates new image instance + * + * @param int $width + * @param int $height + * @param mixed $background + * @return \Intervention\Image\Image + */ + public function newImage($width, $height, $background = null) + { + $background = new Color($background); + + // create empty core + $core = new \Imagick; + $core->newImage($width, $height, $background->getPixel(), 'png'); + $core->setType(\Imagick::IMGTYPE_UNDEFINED); + $core->setImageType(\Imagick::IMGTYPE_UNDEFINED); + $core->setColorspace(\Imagick::COLORSPACE_UNDEFINED); + + // build image + $image = new \Intervention\Image\Image(new static, $core); + + return $image; + } + + /** + * Reads given string into color object + * + * @param string $value + * @return AbstractColor + */ + public function parseColor($value) + { + return new Color($value); + } + + /** + * Checks if core module installation is available + * + * @return boolean + */ + protected function coreAvailable() + { + return (extension_loaded('imagick') && class_exists('Imagick')); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php new file mode 100644 index 0000000000..44452f2479 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Encoder.php @@ -0,0 +1,168 @@ +image->getCore(); + $imagick->setImageBackgroundColor('white'); + $imagick->setBackgroundColor('white'); + $imagick = $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_MERGE); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as PNG string + * + * @return string + */ + protected function processPng() + { + $format = 'png'; + $compression = \Imagick::COMPRESSION_ZIP; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as GIF string + * + * @return string + */ + protected function processGif() + { + $format = 'gif'; + $compression = \Imagick::COMPRESSION_LZW; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + return $imagick->getImagesBlob(); + } + + protected function processWebp() + { + if ( ! \Imagick::queryFormats('WEBP')) { + throw new \Intervention\Image\Exception\NotSupportedException( + "Webp format is not supported by Imagick installation." + ); + } + + $format = 'webp'; + $compression = \Imagick::COMPRESSION_JPEG; + + $imagick = $this->image->getCore(); + $imagick = $imagick->mergeImageLayers(\Imagick::LAYERMETHOD_MERGE); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as TIFF string + * + * @return string + */ + protected function processTiff() + { + $format = 'tiff'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + $imagick->setCompressionQuality($this->quality); + $imagick->setImageCompressionQuality($this->quality); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as BMP string + * + * @return string + */ + protected function processBmp() + { + $format = 'bmp'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as ICO string + * + * @return string + */ + protected function processIco() + { + $format = 'ico'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + return $imagick->getImagesBlob(); + } + + /** + * Processes and returns encoded image as PSD string + * + * @return string + */ + protected function processPsd() + { + $format = 'psd'; + $compression = \Imagick::COMPRESSION_UNDEFINED; + + $imagick = $this->image->getCore(); + $imagick->setFormat($format); + $imagick->setImageFormat($format); + $imagick->setCompression($compression); + $imagick->setImageCompression($compression); + + return $imagick->getImagesBlob(); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php new file mode 100644 index 0000000000..7b926679f2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Font.php @@ -0,0 +1,118 @@ +setStrokeAntialias(true); + $draw->setTextAntialias(true); + + // set font file + if ($this->hasApplicableFontFile()) { + $draw->setFont($this->file); + } else { + throw new \Intervention\Image\Exception\RuntimeException( + "Font file must be provided to apply text to image." + ); + } + + // parse text color + $color = new Color($this->color); + + $draw->setFontSize($this->size); + $draw->setFillColor($color->getPixel()); + + // align horizontal + switch (strtolower($this->align)) { + case 'center': + $align = \Imagick::ALIGN_CENTER; + break; + + case 'right': + $align = \Imagick::ALIGN_RIGHT; + break; + + default: + $align = \Imagick::ALIGN_LEFT; + break; + } + + $draw->setTextAlignment($align); + + // align vertical + if (strtolower($this->valign) != 'bottom') { + + // calculate box size + $dimensions = $image->getCore()->queryFontMetrics($draw, $this->text); + + // corrections on y-position + switch (strtolower($this->valign)) { + case 'center': + case 'middle': + $posy = $posy + $dimensions['textHeight'] * 0.65 / 2; + break; + + case 'top': + $posy = $posy + $dimensions['textHeight'] * 0.65; + break; + } + } + + // apply to image + $image->getCore()->annotateImage($draw, $posx, $posy, $this->angle * (-1), $this->text); + } + + /** + * Calculates bounding box of current font setting + * + * @return array + */ + public function getBoxSize() + { + $box = []; + + // build draw object + $draw = new \ImagickDraw(); + $draw->setStrokeAntialias(true); + $draw->setTextAntialias(true); + + // set font file + if ($this->hasApplicableFontFile()) { + $draw->setFont($this->file); + } else { + throw new \Intervention\Image\Exception\RuntimeException( + "Font file must be provided to apply text to image." + ); + } + + $draw->setFontSize($this->size); + + $dimensions = (new \Imagick())->queryFontMetrics($draw, $this->text); + + if (strlen($this->text) == 0) { + // no text -> no boxsize + $box['width'] = 0; + $box['height'] = 0; + } else { + // get boxsize + $box['width'] = intval(abs($dimensions['textWidth'])); + $box['height'] = intval(abs($dimensions['textHeight'])); + } + + return $box; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php new file mode 100644 index 0000000000..24172ea116 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/CircleShape.php @@ -0,0 +1,40 @@ +width = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->height = is_numeric($diameter) ? intval($diameter) : $this->diameter; + $this->diameter = is_numeric($diameter) ? intval($diameter) : $this->diameter; + } + + /** + * Draw current circle on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + return parent::applyToImage($image, $x, $y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php new file mode 100644 index 0000000000..1b89942fd7 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/EllipseShape.php @@ -0,0 +1,65 @@ +width = is_numeric($width) ? intval($width) : $this->width; + $this->height = is_numeric($height) ? intval($height) : $this->height; + } + + /** + * Draw ellipse instance on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $circle = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $circle->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $circle->setStrokeWidth($this->border_width); + $circle->setStrokeColor($border_color->getPixel()); + } + + $circle->ellipse($x, $y, $this->width / 2, $this->height / 2, 0, 360); + + $image->getCore()->drawImage($circle); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php new file mode 100644 index 0000000000..638b97b67d --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/LineShape.php @@ -0,0 +1,93 @@ +x = is_numeric($x) ? intval($x) : $this->x; + $this->y = is_numeric($y) ? intval($y) : $this->y; + } + + /** + * Set current line color + * + * @param string $color + * @return void + */ + public function color($color) + { + $this->color = $color; + } + + /** + * Set current line width in pixels + * + * @param int $width + * @return void + */ + public function width($width) + { + $this->width = $width; + } + + /** + * Draw current instance of line to given endpoint on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $line = new \ImagickDraw; + + $color = new Color($this->color); + $line->setStrokeColor($color->getPixel()); + $line->setStrokeWidth($this->width); + + $line->line($this->x, $this->y, $x, $y); + $image->getCore()->drawImage($line); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php new file mode 100644 index 0000000000..4c087b3741 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/PolygonShape.php @@ -0,0 +1,80 @@ +points = $this->formatPoints($points); + } + + /** + * Draw polygon on given image + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $polygon = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $polygon->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $polygon->setStrokeWidth($this->border_width); + $polygon->setStrokeColor($border_color->getPixel()); + } + + $polygon->polygon($this->points); + + $image->getCore()->drawImage($polygon); + + return true; + } + + /** + * Format polygon points to Imagick format + * + * @param Array $points + * @return Array + */ + private function formatPoints($points) + { + $ipoints = []; + $count = 1; + + foreach ($points as $key => $value) { + if ($count%2 === 0) { + $y = $value; + $ipoints[] = ['x' => $x, 'y' => $y]; + } else { + $x = $value; + } + $count++; + } + + return $ipoints; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php new file mode 100644 index 0000000000..2d1a09f5c2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Imagick/Shapes/RectangleShape.php @@ -0,0 +1,83 @@ +x1 = is_numeric($x1) ? intval($x1) : $this->x1; + $this->y1 = is_numeric($y1) ? intval($y1) : $this->y1; + $this->x2 = is_numeric($x2) ? intval($x2) : $this->x2; + $this->y2 = is_numeric($y2) ? intval($y2) : $this->y2; + } + + /** + * Draw rectangle to given image at certain position + * + * @param Image $image + * @param int $x + * @param int $y + * @return boolean + */ + public function applyToImage(Image $image, $x = 0, $y = 0) + { + $rectangle = new \ImagickDraw; + + // set background + $bgcolor = new Color($this->background); + $rectangle->setFillColor($bgcolor->getPixel()); + + // set border + if ($this->hasBorder()) { + $border_color = new Color($this->border_color); + $rectangle->setStrokeWidth($this->border_width); + $rectangle->setStrokeColor($border_color->getPixel()); + } + + $rectangle->rectangle($this->x1, $this->y1, $this->x2, $this->y2); + + $image->getCore()->drawImage($rectangle); + + return true; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Point.php b/vendor/intervention/image/src/Intervention/Image/Point.php new file mode 100644 index 0000000000..d51452e026 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Point.php @@ -0,0 +1,64 @@ +x = is_numeric($x) ? intval($x) : 0; + $this->y = is_numeric($y) ? intval($y) : 0; + } + + /** + * Sets X coordinate + * + * @param int $x + */ + public function setX($x) + { + $this->x = intval($x); + } + + /** + * Sets Y coordinate + * + * @param int $y + */ + public function setY($y) + { + $this->y = intval($y); + } + + /** + * Sets both X and Y coordinate + * + * @param int $x + * @param int $y + */ + public function setPosition($x, $y) + { + $this->setX($x); + $this->setY($y); + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Response.php b/vendor/intervention/image/src/Intervention/Image/Response.php new file mode 100644 index 0000000000..8e5c7cacd2 --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Response.php @@ -0,0 +1,69 @@ +image = $image; + $this->format = $format ? $format : $image->mime; + $this->quality = $quality ? $quality : 90; + } + + /** + * Builds response according to settings + * + * @return mixed + */ + public function make() + { + $this->image->encode($this->format, $this->quality); + $data = $this->image->getEncoded(); + $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data); + $length = strlen($data); + + if (function_exists('app') && is_a($app = app(), 'Illuminate\Foundation\Application')) { + + $response = \Illuminate\Support\Facades\Response::make($data); + $response->header('Content-Type', $mime); + $response->header('Content-Length', $length); + + } else { + + header('Content-Type: ' . $mime); + header('Content-Length: ' . $length); + $response = $data; + } + + return $response; + } +} diff --git a/vendor/intervention/image/src/Intervention/Image/Size.php b/vendor/intervention/image/src/Intervention/Image/Size.php new file mode 100644 index 0000000000..667784ca0b --- /dev/null +++ b/vendor/intervention/image/src/Intervention/Image/Size.php @@ -0,0 +1,373 @@ +width = is_numeric($width) ? intval($width) : 1; + $this->height = is_numeric($height) ? intval($height) : 1; + $this->pivot = $pivot ? $pivot : new Point; + } + + /** + * Set the width and height absolutely + * + * @param int $width + * @param int $height + */ + public function set($width, $height) + { + $this->width = $width; + $this->height = $height; + } + + /** + * Set current pivot point + * + * @param Point $point + */ + public function setPivot(Point $point) + { + $this->pivot = $point; + } + + /** + * Get the current width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Get the current height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Calculate the current aspect ratio + * + * @return float + */ + public function getRatio() + { + return $this->width / $this->height; + } + + /** + * Resize to desired width and/or height + * + * @param int $width + * @param int $height + * @param Closure $callback + * @return Size + */ + public function resize($width, $height, Closure $callback = null) + { + if (is_null($width) && is_null($height)) { + throw new \Intervention\Image\Exception\InvalidArgumentException( + "Width or height needs to be defined." + ); + } + + // new size with dominant width + $dominant_w_size = clone $this; + $dominant_w_size->resizeHeight($height, $callback); + $dominant_w_size->resizeWidth($width, $callback); + + // new size with dominant height + $dominant_h_size = clone $this; + $dominant_h_size->resizeWidth($width, $callback); + $dominant_h_size->resizeHeight($height, $callback); + + // decide which size to use + if ($dominant_h_size->fitsInto(new self($width, $height))) { + $this->set($dominant_h_size->width, $dominant_h_size->height); + } else { + $this->set($dominant_w_size->width, $dominant_w_size->height); + } + + return $this; + } + + /** + * Scale size according to given constraints + * + * @param int $width + * @param Closure $callback + * @return Size + */ + private function resizeWidth($width, Closure $callback = null) + { + $constraint = $this->getConstraint($callback); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $max_width = $constraint->getSize()->getWidth(); + $max_height = $constraint->getSize()->getHeight(); + } + + if (is_numeric($width)) { + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->width = ($width > $max_width) ? $max_width : $width; + } else { + $this->width = $width; + } + + if ($constraint->isFixed(Constraint::ASPECTRATIO)) { + $h = intval(round($this->width / $constraint->getSize()->getRatio())); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->height = ($h > $max_height) ? $max_height : $h; + } else { + $this->height = $h; + } + } + } + } + + /** + * Scale size according to given constraints + * + * @param int $height + * @param Closure $callback + * @return Size + */ + private function resizeHeight($height, Closure $callback = null) + { + $constraint = $this->getConstraint($callback); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $max_width = $constraint->getSize()->getWidth(); + $max_height = $constraint->getSize()->getHeight(); + } + + if (is_numeric($height)) { + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->height = ($height > $max_height) ? $max_height : $height; + } else { + $this->height = $height; + } + + if ($constraint->isFixed(Constraint::ASPECTRATIO)) { + $w = intval(round($this->height * $constraint->getSize()->getRatio())); + + if ($constraint->isFixed(Constraint::UPSIZE)) { + $this->width = ($w > $max_width) ? $max_width : $w; + } else { + $this->width = $w; + } + } + } + } + + /** + * Calculate the relative position to another Size + * based on the pivot point settings of both sizes. + * + * @param Size $size + * @return \Intervention\Image\Point + */ + public function relativePosition(Size $size) + { + $x = $this->pivot->x - $size->pivot->x; + $y = $this->pivot->y - $size->pivot->y; + + return new Point($x, $y); + } + + /** + * Resize given Size to best fitting size of current size. + * + * @param Size $size + * @return \Intervention\Image\Size + */ + public function fit(Size $size, $position = 'center') + { + // create size with auto height + $auto_height = clone $size; + + $auto_height->resize($this->width, null, function ($constraint) { + $constraint->aspectRatio(); + }); + + // decide which version to use + if ($auto_height->fitsInto($this)) { + + $size = $auto_height; + + } else { + + // create size with auto width + $auto_width = clone $size; + + $auto_width->resize(null, $this->height, function ($constraint) { + $constraint->aspectRatio(); + }); + + $size = $auto_width; + } + + $this->align($position); + $size->align($position); + $size->setPivot($this->relativePosition($size)); + + return $size; + } + + /** + * Checks if given size fits into current size + * + * @param Size $size + * @return boolean + */ + public function fitsInto(Size $size) + { + return ($this->width <= $size->width) && ($this->height <= $size->height); + } + + /** + * Aligns current size's pivot point to given position + * and moves point automatically by offset. + * + * @param string $position + * @param int $offset_x + * @param int $offset_y + * @return \Intervention\Image\Size + */ + public function align($position, $offset_x = 0, $offset_y = 0) + { + switch (strtolower($position)) { + + case 'top': + case 'top-center': + case 'top-middle': + case 'center-top': + case 'middle-top': + $x = intval($this->width / 2); + $y = 0 + $offset_y; + break; + + case 'top-right': + case 'right-top': + $x = $this->width - $offset_x; + $y = 0 + $offset_y; + break; + + case 'left': + case 'left-center': + case 'left-middle': + case 'center-left': + case 'middle-left': + $x = 0 + $offset_x; + $y = intval($this->height / 2); + break; + + case 'right': + case 'right-center': + case 'right-middle': + case 'center-right': + case 'middle-right': + $x = $this->width - $offset_x; + $y = intval($this->height / 2); + break; + + case 'bottom-left': + case 'left-bottom': + $x = 0 + $offset_x; + $y = $this->height - $offset_y; + break; + + case 'bottom': + case 'bottom-center': + case 'bottom-middle': + case 'center-bottom': + case 'middle-bottom': + $x = intval($this->width / 2); + $y = $this->height - $offset_y; + break; + + case 'bottom-right': + case 'right-bottom': + $x = $this->width - $offset_x; + $y = $this->height - $offset_y; + break; + + case 'center': + case 'middle': + case 'center-center': + case 'middle-middle': + $x = intval($this->width / 2); + $y = intval($this->height / 2); + break; + + default: + case 'top-left': + case 'left-top': + $x = 0 + $offset_x; + $y = 0 + $offset_y; + break; + } + + $this->pivot->setPosition($x, $y); + + return $this; + } + + /** + * Runs constraints on current size + * + * @param Closure $callback + * @return \Intervention\Image\Constraint + */ + private function getConstraint(Closure $callback = null) + { + $constraint = new Constraint(clone $this); + + if (is_callable($callback)) { + $callback($constraint); + } + + return $constraint; + } +} diff --git a/vendor/intervention/image/src/config/config.php b/vendor/intervention/image/src/config/config.php new file mode 100644 index 0000000000..2b1d2c3e11 --- /dev/null +++ b/vendor/intervention/image/src/config/config.php @@ -0,0 +1,20 @@ + 'gd' + +]; diff --git a/vendor/league/flysystem/LICENSE b/vendor/league/flysystem/LICENSE new file mode 100644 index 0000000000..0d16ccc974 --- /dev/null +++ b/vendor/league/flysystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2018 Frank de Jonge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/flysystem/composer.json b/vendor/league/flysystem/composer.json new file mode 100644 index 0000000000..6fc135c901 --- /dev/null +++ b/vendor/league/flysystem/composer.json @@ -0,0 +1,64 @@ +{ + "name": "league/flysystem", + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "filesystem", "filesystems", "files", "storage", "dropbox", "aws", + "abstraction", "s3", "ftp", "sftp", "remote", "webdav", + "file systems", "cloud", "cloud files", "rackspace", "copy.com" + ], + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "ext-fileinfo": "*", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7" + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\Flysystem\\Stub\\": "stub/" + }, + "files": [ + "tests/PHPUnitHacks.php" + ] + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "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", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "config": { + "bin-dir": "bin" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/league/flysystem/src/Adapter/AbstractAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php new file mode 100644 index 0000000000..01e8cda5a3 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php @@ -0,0 +1,71 @@ +pathPrefix = null; + return; + } + + $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator; + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->pathPrefix; + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->getPathPrefix() . ltrim($path, '\\/'); + } + + /** + * Remove a path prefix. + * + * @param string $path + * + * @return string path without the prefix + */ + public function removePathPrefix($path) + { + return substr($path, strlen($this->getPathPrefix())); + } +} diff --git a/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php new file mode 100644 index 0000000000..d42630736d --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php @@ -0,0 +1,628 @@ +safeStorage = new SafeStorage(); + $this->setConfig($config); + } + + /** + * Set the config. + * + * @param array $config + * + * @return $this + */ + public function setConfig(array $config) + { + foreach ($this->configurable as $setting) { + if ( ! isset($config[$setting])) { + continue; + } + + $method = 'set' . ucfirst($setting); + + if (method_exists($this, $method)) { + $this->$method($config[$setting]); + } + } + + return $this; + } + + /** + * Returns the host. + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the host. + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * Set the public permission value. + * + * @param int $permPublic + * + * @return $this + */ + public function setPermPublic($permPublic) + { + $this->permPublic = $permPublic; + + return $this; + } + + /** + * Set the private permission value. + * + * @param int $permPrivate + * + * @return $this + */ + public function setPermPrivate($permPrivate) + { + $this->permPrivate = $permPrivate; + + return $this; + } + + /** + * Returns the ftp port. + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Returns the root folder to work from. + * + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * Set the ftp port. + * + * @param int|string $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + + return $this; + } + + /** + * Set the root folder to work from. + * + * @param string $root + * + * @return $this + */ + public function setRoot($root) + { + $this->root = rtrim($root, '\\/') . $this->separator; + + return $this; + } + + /** + * Returns the ftp username. + * + * @return string username + */ + public function getUsername() + { + $username = $this->safeStorage->retrieveSafely('username'); + + return $username !== null ? $username : 'anonymous'; + } + + /** + * Set ftp username. + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->safeStorage->storeSafely('username', $username); + + return $this; + } + + /** + * Returns the password. + * + * @return string password + */ + public function getPassword() + { + return $this->safeStorage->retrieveSafely('password'); + } + + /** + * Set the ftp password. + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->safeStorage->storeSafely('password', $password); + + return $this; + } + + /** + * Returns the amount of seconds before the connection will timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Set the amount of seconds before the connection should timeout. + * + * @param int $timeout + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + + return $this; + } + + /** + * Return the FTP system type. + * + * @return string + */ + public function getSystemType() + { + return $this->systemType; + } + + /** + * Set the FTP system type (windows or unix). + * + * @param string $systemType + * + * @return $this + */ + public function setSystemType($systemType) + { + $this->systemType = strtolower($systemType); + + return $this; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return $this->listDirectoryContents($directory, $recursive); + } + + abstract protected function listDirectoryContents($directory, $recursive = false); + + /** + * Normalize a directory listing. + * + * @param array $listing + * @param string $prefix + * + * @return array directory listing + */ + protected function normalizeListing(array $listing, $prefix = '') + { + $base = $prefix; + $result = []; + $listing = $this->removeDotDirectories($listing); + + while ($item = array_shift($listing)) { + if (preg_match('#^.*:$#', $item)) { + $base = preg_replace('~^\./*|:$~', '', $item); + continue; + } + + $result[] = $this->normalizeObject($item, $base); + } + + return $this->sortListing($result); + } + + /** + * Sort a directory listing. + * + * @param array $result + * + * @return array sorted listing + */ + protected function sortListing(array $result) + { + $compare = function ($one, $two) { + return strnatcmp($one['path'], $two['path']); + }; + + usort($result, $compare); + + return $result; + } + + /** + * Normalize a file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + * + * @throws NotSupportedException + */ + protected function normalizeObject($item, $base) + { + $systemType = $this->systemType ?: $this->detectSystemType($item); + + if ($systemType === 'unix') { + return $this->normalizeUnixObject($item, $base); + } elseif ($systemType === 'windows') { + return $this->normalizeWindowsObject($item, $base); + } + + throw NotSupportedException::forFtpSystemType($systemType); + } + + /** + * Normalize a Unix file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeUnixObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 7); + + if (count(explode(' ', $item, 9)) !== 9) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($permissions, /* $number */, /* $owner */, /* $group */, $size, /* $month */, /* $day */, /* $time*/, $name) = explode(' ', $item, 9); + $type = $this->detectType($permissions); + $path = $base === '' ? $name : $base . $this->separator . $name; + + if ($type === 'dir') { + return compact('type', 'path'); + } + + $permissions = $this->normalizePermissions($permissions); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + $size = (int) $size; + + return compact('type', 'path', 'visibility', 'size'); + } + + /** + * Normalize a Windows/DOS file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeWindowsObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 3); + + if (count(explode(' ', $item, 4)) !== 4) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($date, $time, $size, $name) = explode(' ', $item, 4); + $path = $base === '' ? $name : $base . $this->separator . $name; + + // Check for the correct date/time format + $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i'; + $dt = DateTime::createFromFormat($format, $date . $time); + $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time"); + + if ($size === '') { + $type = 'dir'; + + return compact('type', 'path', 'timestamp'); + } + + $type = 'file'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $size = (int) $size; + + return compact('type', 'path', 'visibility', 'size', 'timestamp'); + } + + /** + * Get the system type from a listing item. + * + * @param string $item + * + * @return string the system type + */ + protected function detectSystemType($item) + { + return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix'; + } + + /** + * Get the file type from the permissions. + * + * @param string $permissions + * + * @return string file type + */ + protected function detectType($permissions) + { + return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file'; + } + + /** + * Normalize a permissions string. + * + * @param string $permissions + * + * @return int + */ + protected function normalizePermissions($permissions) + { + // remove the type identifier + $permissions = substr($permissions, 1); + + // map the string rights to the numeric counterparts + $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1']; + $permissions = strtr($permissions, $map); + + // split up the permission groups + $parts = str_split($permissions, 3); + + // convert the groups + $mapper = function ($part) { + return array_sum(str_split($part)); + }; + + // converts to decimal number + return octdec(implode('', array_map($mapper, $parts))); + } + + /** + * Filter out dot-directories. + * + * @param array $list + * + * @return array + */ + public function removeDotDirectories(array $list) + { + $filter = function ($line) { + return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line); + }; + + return array_filter($list, $filter); + } + + /** + * @inheritdoc + */ + public function has($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return $this->getMetadata($path); + } + + /** + * Ensure a directory exists. + * + * @param string $dirname + */ + public function ensureDirectory($dirname) + { + $dirname = (string) $dirname; + + if ($dirname !== '' && ! $this->has($dirname)) { + $this->createDir($dirname, new Config()); + } + } + + /** + * @return mixed + */ + public function getConnection() + { + $tries = 0; + + while ( ! $this->isConnected() && $tries < 3) { + $tries++; + $this->disconnect(); + $this->connect(); + } + + return $this->connection; + } + + /** + * Get the public permission value. + * + * @return int + */ + public function getPermPublic() + { + return $this->permPublic; + } + + /** + * Get the private permission value. + * + * @return int + */ + public function getPermPrivate() + { + return $this->permPrivate; + } + + /** + * Disconnect on destruction. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Establish a connection. + */ + abstract public function connect(); + + /** + * Close the connection. + */ + abstract public function disconnect(); + + /** + * Check if a connection is active. + * + * @return bool + */ + abstract public function isConnected(); +} diff --git a/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php new file mode 100644 index 0000000000..c42719e608 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php @@ -0,0 +1,10 @@ +transferMode = $mode; + + return $this; + } + + /** + * Set if Ssl is enabled. + * + * @param bool $ssl + * + * @return $this + */ + public function setSsl($ssl) + { + $this->ssl = (bool) $ssl; + + return $this; + } + + /** + * Set if passive mode should be used. + * + * @param bool $passive + */ + public function setPassive($passive = true) + { + $this->passive = $passive; + } + + /** + * @param bool $ignorePassiveAddress + */ + public function setIgnorePassiveAddress($ignorePassiveAddress) + { + $this->ignorePassiveAddress = $ignorePassiveAddress; + } + + /** + * @param bool $recurseManually + */ + public function setRecurseManually($recurseManually) + { + $this->recurseManually = $recurseManually; + } + + /** + * @param bool $utf8 + */ + public function setUtf8($utf8) + { + $this->utf8 = (bool) $utf8; + } + + /** + * Connect to the FTP server. + */ + public function connect() + { + if ($this->ssl) { + $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } else { + $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } + + if ( ! $this->connection) { + throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); + } + + $this->login(); + $this->setUtf8Mode(); + $this->setConnectionPassiveMode(); + $this->setConnectionRoot(); + $this->isPureFtpd = $this->isPureFtpdServer(); + } + + /** + * Set the connection to UTF-8 mode. + */ + protected function setUtf8Mode() + { + if ($this->utf8) { + $response = ftp_raw($this->connection, "OPTS UTF8 ON"); + if (substr($response[0], 0, 3) !== '200') { + throw new RuntimeException( + 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + } + + /** + * Set the connections to passive mode. + * + * @throws RuntimeException + */ + protected function setConnectionPassiveMode() + { + if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { + ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); + } + + if ( ! ftp_pasv($this->connection, $this->passive)) { + throw new RuntimeException( + 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + + /** + * Set the connection root. + */ + protected function setConnectionRoot() + { + $root = $this->getRoot(); + $connection = $this->connection; + + if ($root && ! ftp_chdir($connection, $root)) { + throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot()); + } + + // Store absolute path for further reference. + // This is needed when creating directories and + // initial root was a relative path, else the root + // would be relative to the chdir'd path. + $this->root = ftp_pwd($connection); + } + + /** + * Login. + * + * @throws RuntimeException + */ + protected function login() + { + set_error_handler(function () {}); + $isLoggedIn = ftp_login( + $this->connection, + $this->getUsername(), + $this->getPassword() + ); + restore_error_handler(); + + if ( ! $isLoggedIn) { + $this->disconnect(); + throw new RuntimeException( + 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( + ) . ', username: ' . $this->getUsername() + ); + } + } + + /** + * Disconnect from the FTP server. + */ + public function disconnect() + { + if (is_resource($this->connection)) { + ftp_close($this->connection); + } + + $this->connection = null; + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $contents); + rewind($stream); + $result = $this->writeStream($path, $stream, $config); + fclose($stream); + + if ($result === false) { + return false; + } + + $result['contents'] = $contents; + $result['mimetype'] = Util::guessMimeType($path, $contents); + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $this->ensureDirectory(Util::dirname($path)); + + if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { + return false; + } + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + } + + $type = 'file'; + + return compact('type', 'path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return $this->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return ftp_rename($this->getConnection(), $path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return ftp_delete($this->getConnection(), $path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $connection = $this->getConnection(); + $contents = array_reverse($this->listDirectoryContents($dirname, false)); + + foreach ($contents as $object) { + if ($object['type'] === 'file') { + if ( ! ftp_delete($connection, $object['path'])) { + return false; + } + } elseif ( ! $this->deleteDir($object['path'])) { + return false; + } + } + + return ftp_rmdir($connection, $dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $connection = $this->getConnection(); + $directories = explode('/', $dirname); + + foreach ($directories as $directory) { + if (false === $this->createActualDirectory($directory, $connection)) { + $this->setConnectionRoot(); + + return false; + } + + ftp_chdir($connection, $directory); + } + + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $dirname]; + } + + /** + * Create a directory. + * + * @param string $directory + * @param resource $connection + * + * @return bool + */ + protected function createActualDirectory($directory, $connection) + { + // List the current directory + $listing = ftp_nlist($connection, '.') ?: []; + + foreach ($listing as $key => $item) { + if (preg_match('~^\./.*~', $item)) { + $listing[$key] = substr($item, 2); + } + } + + if (in_array($directory, $listing, true)) { + return true; + } + + return (boolean) ftp_mkdir($connection, $directory); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $connection = $this->getConnection(); + + if ($path === '') { + return ['type' => 'dir', 'path' => '']; + } + + if (@ftp_chdir($connection, $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $listing = $this->ftpRawlist('-A', str_replace('*', '\\*', $path)); + + if (empty($listing) || in_array('total 0', $listing, true)) { + return false; + } + + if (preg_match('/.* not found/', $listing[0])) { + return false; + } + + if (preg_match('/^total [0-9]*$/', $listing[0])) { + array_shift($listing); + } + + return $this->normalizeObject($listing[0], ''); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + if ( ! $metadata = $this->getMetadata($path)) { + return false; + } + + $metadata['mimetype'] = MimeType::detectByFilename($path); + + return $metadata; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $timestamp = ftp_mdtm($this->getConnection(), $path); + + return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + if ( ! $object = $this->readStream($path)) { + return false; + } + + $object['contents'] = stream_get_contents($object['stream']); + fclose($object['stream']); + unset($object['stream']); + + return $object; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $stream = fopen('php://temp', 'w+b'); + $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); + rewind($stream); + + if ( ! $result) { + fclose($stream); + + return false; + } + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); + + if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $directory = str_replace('*', '\\*', $directory); + + if ($recursive && $this->recurseManually) { + return $this->listDirectoryContentsRecursive($directory); + } + + $options = $recursive ? '-alnR' : '-aln'; + $listing = $this->ftpRawlist($options, $directory); + + return $listing ? $this->normalizeListing($listing, $directory) : []; + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContentsRecursive($directory) + { + $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory); + $output = []; + + foreach ($listing as $item) { + $output[] = $item; + if ($item['type'] !== 'dir') continue; + $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path'])); + } + + return $output; + } + + /** + * Check if the connection is open. + * + * @return bool + * @throws ErrorException + */ + public function isConnected() + { + try { + return is_resource($this->connection) && ftp_rawlist($this->connection, $this->getRoot()) !== false; + } catch (ErrorException $e) { + if (strpos($e->getMessage(), 'ftp_rawlist') === false) { + throw $e; + } + + return false; + } + } + + /** + * @return bool + */ + protected function isPureFtpdServer() + { + $response = ftp_raw($this->connection, 'HELP'); + + return stripos(implode(' ', $response), 'Pure-FTPd') !== false; + } + + /** + * The ftp_rawlist function with optional escaping. + * + * @param string $options + * @param string $path + * + * @return array + */ + protected function ftpRawlist($options, $path) + { + $connection = $this->getConnection(); + + if ($this->isPureFtpd) { + $path = str_replace(' ', '\ ', $path); + } + return ftp_rawlist($connection, $options . ' ' . $path); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Ftpd.php b/vendor/league/flysystem/src/Adapter/Ftpd.php new file mode 100644 index 0000000000..7fcecd09b7 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Ftpd.php @@ -0,0 +1,40 @@ + 'dir', 'path' => '']; + } + + if ( ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) { + return false; + } + + if (substr($object[1], 0, 5) === "ftpd:") { + return false; + } + + return $this->normalizeObject($object[1], ''); + } + + /** + * @inheritdoc + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $listing = ftp_rawlist($this->getConnection(), $directory, $recursive); + + if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) { + return []; + } + + return $this->normalizeListing($listing, $directory); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Local.php b/vendor/league/flysystem/src/Adapter/Local.php new file mode 100644 index 0000000000..3e3c827627 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Local.php @@ -0,0 +1,518 @@ + [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, + ] + ]; + + /** + * @var string + */ + protected $pathSeparator = DIRECTORY_SEPARATOR; + + /** + * @var array + */ + protected $permissionMap; + + /** + * @var int + */ + protected $writeFlags; + /** + * @var int + */ + private $linkHandling; + + /** + * Constructor. + * + * @param string $root + * @param int $writeFlags + * @param int $linkHandling + * @param array $permissions + * + * @throws LogicException + */ + public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = []) + { + $root = is_link($root) ? realpath($root) : $root; + $this->permissionMap = array_replace_recursive(static::$permissions, $permissions); + $this->ensureDirectory($root); + + if ( ! is_dir($root) || ! is_readable($root)) { + throw new LogicException('The root path ' . $root . ' is not readable.'); + } + + $this->setPathPrefix($root); + $this->writeFlags = $writeFlags; + $this->linkHandling = $linkHandling; + } + + /** + * Ensure the root directory exists. + * + * @param string $root root directory path + * + * @return void + * + * @throws Exception in case the root directory can not be created + */ + protected function ensureDirectory($root) + { + if ( ! is_dir($root)) { + $umask = umask(0); + @mkdir($root, $this->permissionMap['dir']['public'], true); + umask($umask); + + if ( ! is_dir($root)) { + throw new Exception(sprintf('Impossible to create the root directory "%s".', $root)); + } + } + } + + /** + * @inheritdoc + */ + public function has($path) + { + $location = $this->applyPathPrefix($path); + + return file_exists($location); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + + if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { + return false; + } + + $type = 'file'; + $result = compact('contents', 'type', 'size', 'path'); + + if ($visibility = $config->get('visibility')) { + $result['visibility'] = $visibility; + $this->setVisibility($path, $visibility); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + $stream = fopen($location, 'w+b'); + + if ( ! $stream) { + return false; + } + + stream_copy_to_stream($resource, $stream); + + if ( ! fclose($stream)) { + return false; + } + + $type = 'file'; + + $result = compact('type', 'path'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $location = $this->applyPathPrefix($path); + $stream = fopen($location, 'rb'); + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $size = file_put_contents($location, $contents, $this->writeFlags); + + if ($size === false) { + return false; + } + + $type = 'file'; + + $result = compact('type', 'path', 'size', 'contents'); + + if ($mimetype = Util::guessMimeType($path, $contents)) { + $result['mimetype'] = $mimetype; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function read($path) + { + $location = $this->applyPathPrefix($path); + $contents = file_get_contents($location); + + if ($contents === false) { + return false; + } + + return ['type' => 'file', 'path' => $path, 'contents' => $contents]; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath)); + $this->ensureDirectory($parentDirectory); + + return rename($location, $destination); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $this->ensureDirectory(dirname($destination)); + + return copy($location, $destination); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $location = $this->applyPathPrefix($path); + + return unlink($location); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $result = []; + $location = $this->applyPathPrefix($directory); + + if ( ! is_dir($location)) { + return []; + } + + $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location); + + foreach ($iterator as $file) { + $path = $this->getFilePath($file); + + if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) { + continue; + } + + $result[] = $this->normalizeFileInfo($file); + } + + return array_filter($result); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $location = $this->applyPathPrefix($path); + $info = new SplFileInfo($location); + + return $this->normalizeFileInfo($info); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $location = $this->applyPathPrefix($path); + $finfo = new Finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->file($location); + + if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty'])) { + $mimetype = Util\MimeType::detectByFilename($location); + } + + return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype]; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4)); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $location = $this->applyPathPrefix($path); + $type = is_dir($location) ? 'dir' : 'file'; + $success = chmod($location, $this->permissionMap[$type][$visibility]); + + if ($success === false) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $location = $this->applyPathPrefix($dirname); + $umask = umask(0); + $visibility = $config->get('visibility', 'public'); + + if ( ! is_dir($location) && ! mkdir($location, $this->permissionMap['dir'][$visibility], true)) { + $return = false; + } else { + $return = ['path' => $dirname, 'type' => 'dir']; + } + + umask($umask); + + return $return; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $location = $this->applyPathPrefix($dirname); + + if ( ! is_dir($location)) { + return false; + } + + $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST); + + /** @var SplFileInfo $file */ + foreach ($contents as $file) { + $this->guardAgainstUnreadableFileInfo($file); + $this->deleteFileInfoObject($file); + } + + return rmdir($location); + } + + /** + * @param SplFileInfo $file + */ + protected function deleteFileInfoObject(SplFileInfo $file) + { + switch ($file->getType()) { + case 'dir': + rmdir($file->getRealPath()); + break; + case 'link': + unlink($file->getPathname()); + break; + default: + unlink($file->getRealPath()); + } + } + + /** + * Normalize the file info. + * + * @param SplFileInfo $file + * + * @return array|void + * + * @throws NotSupportedException + */ + protected function normalizeFileInfo(SplFileInfo $file) + { + if ( ! $file->isLink()) { + return $this->mapFileInfo($file); + } + + if ($this->linkHandling & self::DISALLOW_LINKS) { + throw NotSupportedException::forLink($file); + } + } + + /** + * Get the normalized path from a SplFileInfo object. + * + * @param SplFileInfo $file + * + * @return string + */ + protected function getFilePath(SplFileInfo $file) + { + $location = $file->getPathname(); + $path = $this->removePathPrefix($location); + + return trim(str_replace('\\', '/', $path), '/'); + } + + /** + * @param string $path + * @param int $mode + * + * @return RecursiveIteratorIterator + */ + protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST) + { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + $mode + ); + } + + /** + * @param string $path + * + * @return DirectoryIterator + */ + protected function getDirectoryIterator($path) + { + $iterator = new DirectoryIterator($path); + + return $iterator; + } + + /** + * @param SplFileInfo $file + * + * @return array + */ + protected function mapFileInfo(SplFileInfo $file) + { + $normalized = [ + 'type' => $file->getType(), + 'path' => $this->getFilePath($file), + ]; + + $normalized['timestamp'] = $file->getMTime(); + + if ($normalized['type'] === 'file') { + $normalized['size'] = $file->getSize(); + } + + return $normalized; + } + + /** + * @param SplFileInfo $file + * + * @throws UnreadableFileException + */ + protected function guardAgainstUnreadableFileInfo(SplFileInfo $file) + { + if ( ! $file->isReadable()) { + throw UnreadableFileException::forFileInfo($file); + } + } +} diff --git a/vendor/league/flysystem/src/Adapter/NullAdapter.php b/vendor/league/flysystem/src/Adapter/NullAdapter.php new file mode 100644 index 0000000000..2527087f7c --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/NullAdapter.php @@ -0,0 +1,144 @@ +get('visibility')) { + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return false; + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return []; + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + return compact('visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + return ['path' => $dirname, 'type' => 'dir']; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + return false; + } +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php new file mode 100644 index 0000000000..fc0a747acb --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php @@ -0,0 +1,33 @@ +readStream($path); + + if ($response === false || ! is_resource($response['stream'])) { + return false; + } + + $result = $this->writeStream($newpath, $response['stream'], new Config()); + + if ($result !== false && is_resource($response['stream'])) { + fclose($response['stream']); + } + + return $result !== false; + } + + // Required abstract method + + /** + * @param string $path + * @return resource + */ + abstract public function readStream($path); + + /** + * @param string $path + * @param resource $resource + * @param Config $config + * @return resource + */ + abstract public function writeStream($path, $resource, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php new file mode 100644 index 0000000000..2b31c01d69 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php @@ -0,0 +1,44 @@ +read($path)) { + return false; + } + + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $data['contents']); + rewind($stream); + $data['stream'] = $stream; + unset($data['contents']); + + return $data; + } + + /** + * Reads a file. + * + * @param string $path + * + * @return array|false + * + * @see League\Flysystem\ReadInterface::read() + */ + abstract public function read($path); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php new file mode 100644 index 0000000000..80424960c1 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php @@ -0,0 +1,9 @@ +stream($path, $resource, $config, 'write'); + } + + /** + * Update a file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object or visibility setting + * + * @return mixed false of file metadata + */ + public function updateStream($path, $resource, Config $config) + { + return $this->stream($path, $resource, $config, 'update'); + } + + // Required abstract methods + abstract public function write($pash, $contents, Config $config); + abstract public function update($pash, $contents, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/SynologyFtp.php b/vendor/league/flysystem/src/Adapter/SynologyFtp.php new file mode 100644 index 0000000000..fe0d344cfe --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/SynologyFtp.php @@ -0,0 +1,8 @@ +settings = $settings; + } + + /** + * Get a setting. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + public function get($key, $default = null) + { + if ( ! array_key_exists($key, $this->settings)) { + return $this->getDefault($key, $default); + } + + return $this->settings[$key]; + } + + /** + * Check if an item exists by key. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + if (array_key_exists($key, $this->settings)) { + return true; + } + + return $this->fallback instanceof Config + ? $this->fallback->has($key) + : false; + } + + /** + * Try to retrieve a default setting from a config fallback. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + protected function getDefault($key, $default) + { + if ( ! $this->fallback) { + return $default; + } + + return $this->fallback->get($key, $default); + } + + /** + * Set a setting. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + $this->settings[$key] = $value; + + return $this; + } + + /** + * Set the fallback. + * + * @param Config $fallback + * + * @return $this + */ + public function setFallback(Config $fallback) + { + $this->fallback = $fallback; + + return $this; + } +} diff --git a/vendor/league/flysystem/src/ConfigAwareTrait.php b/vendor/league/flysystem/src/ConfigAwareTrait.php new file mode 100644 index 0000000000..202d605dad --- /dev/null +++ b/vendor/league/flysystem/src/ConfigAwareTrait.php @@ -0,0 +1,49 @@ +config = $config ? Util::ensureConfig($config) : new Config; + } + + /** + * Get the Config. + * + * @return Config config object + */ + public function getConfig() + { + return $this->config; + } + + /** + * Convert a config array to a Config object with the correct fallback. + * + * @param array $config + * + * @return Config + */ + protected function prepareConfig(array $config) + { + $config = new Config($config); + $config->setFallback($this->getConfig()); + + return $config; + } +} diff --git a/vendor/league/flysystem/src/Directory.php b/vendor/league/flysystem/src/Directory.php new file mode 100644 index 0000000000..d4f90a8804 --- /dev/null +++ b/vendor/league/flysystem/src/Directory.php @@ -0,0 +1,31 @@ +filesystem->deleteDir($this->path); + } + + /** + * List the directory contents. + * + * @param bool $recursive + * + * @return array|bool directory contents or false + */ + public function getContents($recursive = false) + { + return $this->filesystem->listContents($this->path, $recursive); + } +} diff --git a/vendor/league/flysystem/src/Exception.php b/vendor/league/flysystem/src/Exception.php new file mode 100644 index 0000000000..d4a9907b4a --- /dev/null +++ b/vendor/league/flysystem/src/Exception.php @@ -0,0 +1,8 @@ +filesystem->has($this->path); + } + + /** + * Read the file. + * + * @return string|false file contents + */ + public function read() + { + return $this->filesystem->read($this->path); + } + + /** + * Read the file as a stream. + * + * @return resource|false file stream + */ + public function readStream() + { + return $this->filesystem->readStream($this->path); + } + + /** + * Write the new file. + * + * @param string $content + * + * @return bool success boolean + */ + public function write($content) + { + return $this->filesystem->write($this->path, $content); + } + + /** + * Write the new file using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function writeStream($resource) + { + return $this->filesystem->writeStream($this->path, $resource); + } + + /** + * Update the file contents. + * + * @param string $content + * + * @return bool success boolean + */ + public function update($content) + { + return $this->filesystem->update($this->path, $content); + } + + /** + * Update the file contents with a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function updateStream($resource) + { + return $this->filesystem->updateStream($this->path, $resource); + } + + /** + * Create the file or update if exists. + * + * @param string $content + * + * @return bool success boolean + */ + public function put($content) + { + return $this->filesystem->put($this->path, $content); + } + + /** + * Create the file or update if exists using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function putStream($resource) + { + return $this->filesystem->putStream($this->path, $resource); + } + + /** + * Rename the file. + * + * @param string $newpath + * + * @return bool success boolean + */ + public function rename($newpath) + { + if ($this->filesystem->rename($this->path, $newpath)) { + $this->path = $newpath; + + return true; + } + + return false; + } + + /** + * Copy the file. + * + * @param string $newpath + * + * @return File|false new file or false + */ + public function copy($newpath) + { + if ($this->filesystem->copy($this->path, $newpath)) { + return new File($this->filesystem, $newpath); + } + + return false; + } + + /** + * Get the file's timestamp. + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp() + { + return $this->filesystem->getTimestamp($this->path); + } + + /** + * Get the file's mimetype. + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype() + { + return $this->filesystem->getMimetype($this->path); + } + + /** + * Get the file's visibility. + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility() + { + return $this->filesystem->getVisibility($this->path); + } + + /** + * Get the file's metadata. + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata() + { + return $this->filesystem->getMetadata($this->path); + } + + /** + * Get the file size. + * + * @return int|false The file size or false on failure. + */ + public function getSize() + { + return $this->filesystem->getSize($this->path); + } + + /** + * Delete the file. + * + * @return bool success boolean + */ + public function delete() + { + return $this->filesystem->delete($this->path); + } +} diff --git a/vendor/league/flysystem/src/FileExistsException.php b/vendor/league/flysystem/src/FileExistsException.php new file mode 100644 index 0000000000..c82e20c169 --- /dev/null +++ b/vendor/league/flysystem/src/FileExistsException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/FileNotFoundException.php b/vendor/league/flysystem/src/FileNotFoundException.php new file mode 100644 index 0000000000..989df69bbe --- /dev/null +++ b/vendor/league/flysystem/src/FileNotFoundException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was not found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/Filesystem.php b/vendor/league/flysystem/src/Filesystem.php new file mode 100644 index 0000000000..7e9881fb54 --- /dev/null +++ b/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,407 @@ +adapter = $adapter; + $this->setConfig($config); + } + + /** + * Get the Adapter. + * + * @return AdapterInterface adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * @inheritdoc + */ + public function has($path) + { + $path = Util::normalizePath($path); + + return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function put($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function putStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + Util::rewindStream($resource); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles &&$this->has($path)) { + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function readAndDelete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + $contents = $this->read($path); + + if ($contents === false) { + return false; + } + + $this->delete($path); + + return $contents; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + $this->assertPresent($path); + + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + $this->assertPresent($path); + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function read($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! ($object = $this->getAdapter()->read($path))) { + return false; + } + + return $object['contents']; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! $object = $this->getAdapter()->readStream($path)) { + return false; + } + + return $object['stream']; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return (bool) $this->getAdapter()->rename($path, $newpath); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return $this->getAdapter()->copy($path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->delete($path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $dirname = Util::normalizePath($dirname); + + if ($dirname === '') { + throw new RootViolationException('Root directories can not be deleted.'); + } + + return (bool) $this->getAdapter()->deleteDir($dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, array $config = []) + { + $dirname = Util::normalizePath($dirname); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->createDir($dirname, $config); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $directory = Util::normalizePath($directory); + $contents = $this->getAdapter()->listContents($directory, $recursive); + + return (new ContentListingFormatter($directory, $recursive))->formatListing($contents); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) { + return false; + } + + return $object['mimetype']; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) { + return false; + } + + return $object['timestamp']; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) { + return false; + } + + return $object['visibility']; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) { + return false; + } + + return (int) $object['size']; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return (bool) $this->getAdapter()->setVisibility($path, $visibility); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function get($path, Handler $handler = null) + { + $path = Util::normalizePath($path); + + if ( ! $handler) { + $metadata = $this->getMetadata($path); + $handler = $metadata['type'] === 'file' ? new File($this, $path) : new Directory($this, $path); + } + + $handler->setPath($path); + $handler->setFilesystem($this); + + return $handler; + } + + /** + * Assert a file is present. + * + * @param string $path path to file + * + * @throws FileNotFoundException + * + * @return void + */ + public function assertPresent($path) + { + if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) { + throw new FileNotFoundException($path); + } + } + + /** + * Assert a file is absent. + * + * @param string $path path to file + * + * @throws FileExistsException + * + * @return void + */ + public function assertAbsent($path) + { + if ($this->config->get('disable_asserts', false) === false && $this->has($path)) { + throw new FileExistsException($path); + } + } +} diff --git a/vendor/league/flysystem/src/FilesystemInterface.php b/vendor/league/flysystem/src/FilesystemInterface.php new file mode 100644 index 0000000000..09b811b1d2 --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemInterface.php @@ -0,0 +1,284 @@ +path = $path; + $this->filesystem = $filesystem; + } + + /** + * Check whether the entree is a directory. + * + * @return bool + */ + public function isDir() + { + return $this->getType() === 'dir'; + } + + /** + * Check whether the entree is a file. + * + * @return bool + */ + public function isFile() + { + return $this->getType() === 'file'; + } + + /** + * Retrieve the entree type (file|dir). + * + * @return string file or dir + */ + public function getType() + { + $metadata = $this->filesystem->getMetadata($this->path); + + return $metadata['type']; + } + + /** + * Set the Filesystem object. + * + * @param FilesystemInterface $filesystem + * + * @return $this + */ + public function setFilesystem(FilesystemInterface $filesystem) + { + $this->filesystem = $filesystem; + + return $this; + } + + /** + * Retrieve the Filesystem object. + * + * @return FilesystemInterface + */ + public function getFilesystem() + { + return $this->filesystem; + } + + /** + * Set the entree path. + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Retrieve the entree path. + * + * @return string path + */ + public function getPath() + { + return $this->path; + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, array $arguments) + { + array_unshift($arguments, $this->path); + $callback = [$this->filesystem, $method]; + + try { + return call_user_func_array($callback, $arguments); + } catch (BadMethodCallException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_called_class() + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/MountManager.php b/vendor/league/flysystem/src/MountManager.php new file mode 100644 index 0000000000..6dea200103 --- /dev/null +++ b/vendor/league/flysystem/src/MountManager.php @@ -0,0 +1,320 @@ + Filesystem,] + * + * @throws InvalidArgumentException + */ + public function __construct(array $filesystems = []) + { + $this->mountFilesystems($filesystems); + } + + /** + * Mount filesystems. + * + * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystems(array $filesystems) + { + foreach ($filesystems as $prefix => $filesystem) { + $this->mountFilesystem($prefix, $filesystem); + } + + return $this; + } + + /** + * Mount filesystems. + * + * @param string $prefix + * @param FilesystemInterface $filesystem + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystem($prefix, FilesystemInterface $filesystem) + { + if ( ! is_string($prefix)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); + } + + $this->filesystems[$prefix] = $filesystem; + + return $this; + } + + /** + * Get the filesystem with the corresponding prefix. + * + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return FilesystemInterface + */ + public function getFilesystem($prefix) + { + if ( ! isset($this->filesystems[$prefix])) { + throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); + } + + return $this->filesystems[$prefix]; + } + + /** + * Retrieve the prefix from an arguments array. + * + * @param array $arguments + * + * @throws InvalidArgumentException + * + * @return array [:prefix, :arguments] + */ + public function filterPrefix(array $arguments) + { + if (empty($arguments)) { + throw new InvalidArgumentException('At least one argument needed'); + } + + $path = array_shift($arguments); + + if ( ! is_string($path)) { + throw new InvalidArgumentException('First argument should be a string'); + } + + list($prefix, $path) = $this->getPrefixAndPath($path); + array_unshift($arguments, $path); + + return [$prefix, $arguments]; + } + + /** + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listContents($directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $filesystem = $this->getFilesystem($prefix); + $result = $filesystem->listContents($directory, $recursive); + + foreach ($result as &$file) { + $file['filesystem'] = $prefix; + } + + return $result; + } + + /** + * Call forwarder. + * + * @param string $method + * @param array $arguments + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function __call($method, $arguments) + { + list($prefix, $arguments) = $this->filterPrefix($arguments); + + return $this->invokePluginOnFilesystem($method, $arguments, $prefix); + } + + /** + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return bool + */ + public function copy($from, $to, array $config = []) + { + list($prefixFrom, $from) = $this->getPrefixAndPath($from); + + $buffer = $this->getFilesystem($prefixFrom)->readStream($from); + + if ($buffer === false) { + return false; + } + + list($prefixTo, $to) = $this->getPrefixAndPath($to); + + $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); + + if (is_resource($buffer)) { + fclose($buffer); + } + + return $result; + } + + /** + * List with plugin adapter. + * + * @param array $keys + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listWith(array $keys = [], $directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $arguments = [$keys, $directory, $recursive]; + + return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); + } + + /** + * Move a file. + * + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return bool + */ + public function move($from, $to, array $config = []) + { + list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); + list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); + + if ($prefixFrom === $prefixTo) { + $filesystem = $this->getFilesystem($prefixFrom); + $renamed = $filesystem->rename($pathFrom, $pathTo); + + if ($renamed && isset($config['visibility'])) { + return $filesystem->setVisibility($pathTo, $config['visibility']); + } + + return $renamed; + } + + $copied = $this->copy($from, $to, $config); + + if ($copied) { + return $this->delete($from); + } + + return false; + } + + /** + * Invoke a plugin on a filesystem mounted on a given prefix. + * + * @param string $method + * @param array $arguments + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function invokePluginOnFilesystem($method, $arguments, $prefix) + { + $filesystem = $this->getFilesystem($prefix); + + try { + return $this->invokePlugin($method, $arguments, $filesystem); + } catch (PluginNotFoundException $e) { + // Let it pass, it's ok, don't panic. + } + + $callback = [$filesystem, $method]; + + return call_user_func_array($callback, $arguments); + } + + /** + * @param string $path + * + * @throws InvalidArgumentException + * + * @return string[] [:prefix, :path] + */ + protected function getPrefixAndPath($path) + { + if (strpos($path, '://') < 1) { + throw new InvalidArgumentException('No prefix detected in path: ' . $path); + } + + return explode('://', $path, 2); + } +} diff --git a/vendor/league/flysystem/src/NotSupportedException.php b/vendor/league/flysystem/src/NotSupportedException.php new file mode 100644 index 0000000000..08f47f7495 --- /dev/null +++ b/vendor/league/flysystem/src/NotSupportedException.php @@ -0,0 +1,37 @@ +getPathname()); + } + + /** + * Create a new exception for a link. + * + * @param string $systemType + * + * @return static + */ + public static function forFtpSystemType($systemType) + { + $message = "The FTP system type '$systemType' is currently not supported."; + + return new static($message); + } +} diff --git a/vendor/league/flysystem/src/Plugin/AbstractPlugin.php b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php new file mode 100644 index 0000000000..0d56789769 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php @@ -0,0 +1,24 @@ +filesystem = $filesystem; + } +} diff --git a/vendor/league/flysystem/src/Plugin/EmptyDir.php b/vendor/league/flysystem/src/Plugin/EmptyDir.php new file mode 100644 index 0000000000..b5ae7f582d --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/EmptyDir.php @@ -0,0 +1,34 @@ +filesystem->listContents($dirname, false); + + foreach ($listing as $item) { + if ($item['type'] === 'dir') { + $this->filesystem->deleteDir($item['path']); + } else { + $this->filesystem->delete($item['path']); + } + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedCopy.php b/vendor/league/flysystem/src/Plugin/ForcedCopy.php new file mode 100644 index 0000000000..a41e9f3aeb --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedCopy.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->copy($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedRename.php b/vendor/league/flysystem/src/Plugin/ForcedRename.php new file mode 100644 index 0000000000..3f51cd6076 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedRename.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->rename($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/GetWithMetadata.php b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php new file mode 100644 index 0000000000..6fe4f05628 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php @@ -0,0 +1,51 @@ +filesystem->getMetadata($path); + + if ( ! $object) { + return false; + } + + $keys = array_diff($metadata, array_keys($object)); + + foreach ($keys as $key) { + if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) { + throw new InvalidArgumentException('Could not fetch metadata: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($path); + } + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListFiles.php b/vendor/league/flysystem/src/Plugin/ListFiles.php new file mode 100644 index 0000000000..9669fe7e7b --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListFiles.php @@ -0,0 +1,35 @@ +filesystem->listContents($directory, $recursive); + + $filter = function ($object) { + return $object['type'] === 'file'; + }; + + return array_values(array_filter($contents, $filter)); + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListPaths.php b/vendor/league/flysystem/src/Plugin/ListPaths.php new file mode 100644 index 0000000000..514bdf0b3a --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListPaths.php @@ -0,0 +1,36 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $object) { + $result[] = $object['path']; + } + + return $result; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListWith.php b/vendor/league/flysystem/src/Plugin/ListWith.php new file mode 100644 index 0000000000..d90464e528 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListWith.php @@ -0,0 +1,60 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $index => $object) { + if ($object['type'] === 'file') { + $missingKeys = array_diff($keys, array_keys($object)); + $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object); + } + } + + return $contents; + } + + /** + * Get a meta-data value by key name. + * + * @param array $object + * @param string $key + * + * @return array + */ + protected function getMetadataByName(array $object, $key) + { + $method = 'get' . ucfirst($key); + + if ( ! method_exists($this->filesystem, $method)) { + throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($object['path']); + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluggableTrait.php b/vendor/league/flysystem/src/Plugin/PluggableTrait.php new file mode 100644 index 0000000000..922edfe52b --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluggableTrait.php @@ -0,0 +1,97 @@ +plugins[$plugin->getMethod()] = $plugin; + + return $this; + } + + /** + * Find a specific plugin. + * + * @param string $method + * + * @throws PluginNotFoundException + * + * @return PluginInterface + */ + protected function findPlugin($method) + { + if ( ! isset($this->plugins[$method])) { + throw new PluginNotFoundException('Plugin not found for method: ' . $method); + } + + return $this->plugins[$method]; + } + + /** + * Invoke a plugin by method name. + * + * @param string $method + * @param array $arguments + * @param FilesystemInterface $filesystem + * + * @throws PluginNotFoundException + * + * @return mixed + */ + protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) + { + $plugin = $this->findPlugin($method); + $plugin->setFilesystem($filesystem); + $callback = [$plugin, 'handle']; + + return call_user_func_array($callback, $arguments); + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @throws BadMethodCallException + * + * @return mixed + */ + public function __call($method, array $arguments) + { + try { + return $this->invokePlugin($method, $arguments, $this); + } catch (PluginNotFoundException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_class($this) + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php new file mode 100644 index 0000000000..fd1d7e7e30 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php @@ -0,0 +1,10 @@ +hash = spl_object_hash($this); + static::$safeStorage[$this->hash] = []; + } + + public function storeSafely($key, $value) + { + static::$safeStorage[$this->hash][$key] = $value; + } + + public function retrieveSafely($key) + { + if (array_key_exists($key, static::$safeStorage[$this->hash])) { + return static::$safeStorage[$this->hash][$key]; + } + } + + public function __destruct() + { + unset(static::$safeStorage[$this->hash]); + } +} diff --git a/vendor/league/flysystem/src/UnreadableFileException.php b/vendor/league/flysystem/src/UnreadableFileException.php new file mode 100644 index 0000000000..e66803383f --- /dev/null +++ b/vendor/league/flysystem/src/UnreadableFileException.php @@ -0,0 +1,18 @@ +getRealPath() + ) + ); + } +} diff --git a/vendor/league/flysystem/src/Util.php b/vendor/league/flysystem/src/Util.php new file mode 100644 index 0000000000..46dad86f15 --- /dev/null +++ b/vendor/league/flysystem/src/Util.php @@ -0,0 +1,348 @@ + '']; + } + + /** + * Normalize a dirname return value. + * + * @param string $dirname + * + * @return string normalized dirname + */ + public static function normalizeDirname($dirname) + { + return $dirname === '.' ? '' : $dirname; + } + + /** + * Get a normalized dirname from a path. + * + * @param string $path + * + * @return string dirname + */ + public static function dirname($path) + { + return static::normalizeDirname(dirname($path)); + } + + /** + * Map result arrays. + * + * @param array $object + * @param array $map + * + * @return array mapped result + */ + public static function map(array $object, array $map) + { + $result = []; + + foreach ($map as $from => $to) { + if ( ! isset($object[$from])) { + continue; + } + + $result[$to] = $object[$from]; + } + + return $result; + } + + /** + * Normalize path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizePath($path) + { + return static::normalizeRelativePath($path); + } + + /** + * Normalize relative directories in a path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizeRelativePath($path) + { + $path = str_replace('\\', '/', $path); + $path = static::removeFunkyWhiteSpace($path); + + $parts = []; + + foreach (explode('/', $path) as $part) { + switch ($part) { + case '': + case '.': + break; + + case '..': + if (empty($parts)) { + throw new LogicException( + 'Path is outside of the defined root, path: [' . $path . ']' + ); + } + array_pop($parts); + break; + + default: + $parts[] = $part; + break; + } + } + + return implode('/', $parts); + } + + /** + * Removes unprintable characters and invalid unicode characters. + * + * @param string $path + * + * @return string $path + */ + protected static function removeFunkyWhiteSpace($path) { + // We do this check in a loop, since removing invalid unicode characters + // can lead to new characters being created. + while (preg_match('#\p{C}+|^\./#u', $path)) { + $path = preg_replace('#\p{C}+|^\./#u', '', $path); + } + + return $path; + } + + /** + * Normalize prefix. + * + * @param string $prefix + * @param string $separator + * + * @return string normalized path + */ + public static function normalizePrefix($prefix, $separator) + { + return rtrim($prefix, $separator) . $separator; + } + + /** + * Get content size. + * + * @param string $contents + * + * @return int content size + */ + public static function contentSize($contents) + { + return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); + } + + /** + * Guess MIME Type based on the path of the file and it's content. + * + * @param string $path + * @param string|resource $content + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function guessMimeType($path, $content) + { + $mimeType = MimeType::detectByContent($content); + + if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { + return $mimeType; + } + + return MimeType::detectByFilename($path); + } + + /** + * Emulate directories. + * + * @param array $listing + * + * @return array listing with emulated directories + */ + public static function emulateDirectories(array $listing) + { + $directories = []; + $listedDirectories = []; + + foreach ($listing as $object) { + list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories); + } + + $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); + + foreach ($directories as $directory) { + $listing[] = static::pathinfo($directory) + ['type' => 'dir']; + } + + return $listing; + } + + /** + * Ensure a Config instance. + * + * @param null|array|Config $config + * + * @return Config config instance + * + * @throw LogicException + */ + public static function ensureConfig($config) + { + if ($config === null) { + return new Config(); + } + + if ($config instanceof Config) { + return $config; + } + + if (is_array($config)) { + return new Config($config); + } + + throw new LogicException('A config should either be an array or a Flysystem\Config object.'); + } + + /** + * Rewind a stream. + * + * @param resource $resource + */ + public static function rewindStream($resource) + { + if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { + rewind($resource); + } + } + + public static function isSeekableStream($resource) + { + $metadata = stream_get_meta_data($resource); + + return $metadata['seekable']; + } + + /** + * Get the size of a stream. + * + * @param resource $resource + * + * @return int stream size + */ + public static function getStreamSize($resource) + { + $stat = fstat($resource); + + return $stat['size']; + } + + /** + * Emulate the directories of a single object. + * + * @param array $object + * @param array $directories + * @param array $listedDirectories + * + * @return array + */ + protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) + { + if ($object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + } + + if (empty($object['dirname'])) { + return [$directories, $listedDirectories]; + } + + $parent = $object['dirname']; + + while ( ! empty($parent) && ! in_array($parent, $directories)) { + $directories[] = $parent; + $parent = static::dirname($parent); + } + + if (isset($object['type']) && $object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + + return [$directories, $listedDirectories]; + } + + return [$directories, $listedDirectories]; + } + + /** + * Returns the trailing name component of the path. + * + * @param string $path + * + * @return string + */ + private static function basename($path) + { + $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; + + $path = rtrim($path, $separators); + + $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); + + if (DIRECTORY_SEPARATOR === '/') { + return $basename; + } + // @codeCoverageIgnoreStart + // Extra Windows path munging. This is tested via AppVeyor, but code + // coverage is not reported. + + // Handle relative paths with drive letters. c:file.txt. + while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { + $basename = substr($basename, 2); + } + + // Remove colon for standalone drive letter names. + if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { + $basename = rtrim($basename, ':'); + } + + return $basename; + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/league/flysystem/src/Util/ContentListingFormatter.php b/vendor/league/flysystem/src/Util/ContentListingFormatter.php new file mode 100644 index 0000000000..5a8c95a8d9 --- /dev/null +++ b/vendor/league/flysystem/src/Util/ContentListingFormatter.php @@ -0,0 +1,116 @@ +directory = $directory; + $this->recursive = $recursive; + } + + /** + * Format contents listing. + * + * @param array $listing + * + * @return array + */ + public function formatListing(array $listing) + { + $listing = array_values( + array_map( + [$this, 'addPathInfo'], + array_filter($listing, [$this, 'isEntryOutOfScope']) + ) + ); + + return $this->sortListing($listing); + } + + private function addPathInfo(array $entry) + { + return $entry + Util::pathinfo($entry['path']); + } + + /** + * Determine if the entry is out of scope. + * + * @param array $entry + * + * @return bool + */ + private function isEntryOutOfScope(array $entry) + { + if (empty($entry['path']) && $entry['path'] !== '0') { + return false; + } + + if ($this->recursive) { + return $this->residesInDirectory($entry); + } + + return $this->isDirectChild($entry); + } + + /** + * Check if the entry resides within the parent directory. + * + * @param array $entry + * + * @return bool + */ + private function residesInDirectory(array $entry) + { + if ($this->directory === '') { + return true; + } + + return strpos($entry['path'], $this->directory . '/') === 0; + } + + /** + * Check if the entry is a direct child of the directory. + * + * @param array $entry + * + * @return bool + */ + private function isDirectChild(array $entry) + { + return Util::dirname($entry['path']) === $this->directory; + } + + /** + * @param array $listing + * + * @return array + */ + private function sortListing(array $listing) + { + usort($listing, function ($a, $b) { + return strcasecmp($a['path'], $b['path']); + }); + + return $listing; + } +} diff --git a/vendor/league/flysystem/src/Util/MimeType.php b/vendor/league/flysystem/src/Util/MimeType.php new file mode 100644 index 0000000000..69e35e729a --- /dev/null +++ b/vendor/league/flysystem/src/Util/MimeType.php @@ -0,0 +1,232 @@ +buffer($content) ?: null; + // @codeCoverageIgnoreStart + } catch( ErrorException $e ) { + // This is caused by an array to string conversion error. + } + } // @codeCoverageIgnoreEnd + + /** + * Detects MIME Type based on file extension. + * + * @param string $extension + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function detectByFileExtension($extension) + { + static $extensionToMimeTypeMap; + + if (! $extensionToMimeTypeMap) { + $extensionToMimeTypeMap = static::getExtensionToMimeTypeMap(); + } + + if (isset($extensionToMimeTypeMap[$extension])) { + return $extensionToMimeTypeMap[$extension]; + } + + return 'text/plain'; + } + + /** + * @param string $filename + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function detectByFilename($filename) + { + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension); + } + + /** + * @return array Map of file extension to MIME Type + */ + public static function getExtensionToMimeTypeMap() + { + return [ + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'csv' => 'text/x-comma-separated-values', + 'bin' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'class' => 'application/octet-stream', + 'psd' => 'application/x-photoshop', + 'so' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/pdf', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'wbxml' => 'application/wbxml', + 'wmlc' => 'application/wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'php' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'z' => 'application/x-compress', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'zip' => 'application/x-zip', + 'rar' => 'application/x-rar', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'rv' => 'video/vnd.rn-realvideo', + 'wav' => 'audio/x-wav', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'svg' => 'image/svg+xml', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'txt' => 'text/plain', + 'text' => 'text/plain', + 'log' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dmn' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dot' => 'application/msword', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'word' => 'application/msword', + 'xl' => 'application/excel', + 'eml' => 'message/rfc822', + 'json' => 'application/json', + 'pem' => 'application/x-x509-user-cert', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'crt' => 'application/x-x509-ca-cert', + 'crl' => 'application/pkix-crl', + 'der' => 'application/x-x509-ca-cert', + 'kdb' => 'application/octet-stream', + 'pgp' => 'application/pgp', + 'gpg' => 'application/gpg-keys', + 'sst' => 'application/octet-stream', + 'csr' => 'application/octet-stream', + 'rsa' => 'application/x-pkcs7', + 'cer' => 'application/pkix-cert', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + 'mp4' => 'video/mp4', + 'm4a' => 'audio/x-m4a', + 'f4v' => 'video/mp4', + 'webm' => 'video/webm', + 'aac' => 'audio/x-acc', + 'm4u' => 'application/vnd.mpegurl', + 'm3u' => 'text/plain', + 'xspf' => 'application/xspf+xml', + 'vlc' => 'application/videolan', + 'wmv' => 'video/x-ms-wmv', + 'au' => 'audio/x-au', + 'ac3' => 'audio/ac3', + 'flac' => 'audio/x-flac', + 'ogg' => 'audio/ogg', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'ics' => 'text/calendar', + 'zsh' => 'text/x-scriptzsh', + '7zip' => 'application/x-7z-compressed', + 'cdr' => 'application/cdr', + 'wma' => 'audio/x-ms-wma', + 'jar' => 'application/java-archive', + 'tex' => 'application/x-tex', + 'latex' => 'application/x-latex', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + ]; + } +} diff --git a/vendor/league/flysystem/src/Util/StreamHasher.php b/vendor/league/flysystem/src/Util/StreamHasher.php new file mode 100644 index 0000000000..938ec5db71 --- /dev/null +++ b/vendor/league/flysystem/src/Util/StreamHasher.php @@ -0,0 +1,36 @@ +algo = $algo; + } + + /** + * @param resource $resource + * + * @return string + */ + public function hash($resource) + { + rewind($resource); + $context = hash_init($this->algo); + hash_update_stream($context, $resource); + fclose($resource); + + return hash_final($context); + } +} diff --git a/vendor/league/oauth1-client/.scrutinizer.yml b/vendor/league/oauth1-client/.scrutinizer.yml new file mode 100644 index 0000000000..1d352e5bfe --- /dev/null +++ b/vendor/league/oauth1-client/.scrutinizer.yml @@ -0,0 +1,35 @@ +filter: + excluded_paths: [tests/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 600 + runs: 4 + php_analyzer: true + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, tests] + php_cpd: + enabled: true + excluded_dirs: [vendor, tests] diff --git a/vendor/league/oauth1-client/.travis.yml b/vendor/league/oauth1-client/.travis.yml new file mode 100644 index 0000000000..0644d45ba7 --- /dev/null +++ b/vendor/league/oauth1-client/.travis.yml @@ -0,0 +1,22 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +before_script: + - travis_retry composer self-update + - travis_retry composer install --no-interaction --prefer-source --dev + - travis_retry phpenv rehash + +script: + - ./vendor/bin/phpcs --standard=psr2 src/ + - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover + +after_script: + - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ] && [ "$TRAVIS_PHP_VERSION" != "7.0" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi + - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ] && [ "$TRAVIS_PHP_VERSION" != "7.0" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi diff --git a/vendor/league/oauth1-client/CONDUCT.md b/vendor/league/oauth1-client/CONDUCT.md new file mode 100644 index 0000000000..6ff94ca3a4 --- /dev/null +++ b/vendor/league/oauth1-client/CONDUCT.md @@ -0,0 +1,22 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community in a direct capacity. Personal views, beliefs and values of individuals do not necessarily reflect those of the organisation or affiliated individuals and organisations. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) diff --git a/vendor/league/oauth1-client/CONTRIBUTING.md b/vendor/league/oauth1-client/CONTRIBUTING.md new file mode 100644 index 0000000000..576bb1f35e --- /dev/null +++ b/vendor/league/oauth1-client/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth1-client). + + +## Pull Requests + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow semver. Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + + +## Running Tests + +``` bash +$ phpunit +``` + + +**Happy coding**! diff --git a/vendor/league/oauth1-client/LICENSE b/vendor/league/oauth1-client/LICENSE new file mode 100644 index 0000000000..922a514183 --- /dev/null +++ b/vendor/league/oauth1-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Ben Corlett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/oauth1-client/README.md b/vendor/league/oauth1-client/README.md new file mode 100644 index 0000000000..550718d89b --- /dev/null +++ b/vendor/league/oauth1-client/README.md @@ -0,0 +1,260 @@ +# OAuth 1.0 Client + +[![Latest Stable Version](https://img.shields.io/github/release/thephpleague/oauth1-client.svg?style=flat-square)](https://github.com/thephpleague/oauth1-client/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/thephpleague/oauth1-client/master.svg?style=flat-square&1)](https://travis-ci.org/thephpleague/oauth1-client) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth1-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth1-client/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth1-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth1-client) +[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth1-client.svg?style=flat-square)](https://packagist.org/packages/thephpleague/oauth1-client) + +OAuth 1 Client is an OAuth [RFC 5849 standards-compliant](http://tools.ietf.org/html/rfc5849) library for authenticating against OAuth 1 servers. + +It has built in support for: + +- Bitbucket +- Trello +- Tumblr +- Twitter +- Xing + +Adding support for other providers is trivial. The library requires PHP 5.3+ and is PSR-2 compatible. + +### Third-Party Providers + +If you would like to support other providers, please make them available as a Composer package, then link to them +below. + +These providers allow integration with other providers not supported by `oauth1-client`. They may require an older version +so please help them out with a pull request if you notice this. + +- [Intuit](https://packagist.org/packages/wheniwork/oauth1-intuit) +- [500px](https://packagist.org/packages/mechant/oauth1-500px) +- [Etsy](https://packagist.org/packages/y0lk/oauth1-etsy) +- [Xero](https://packagist.org/packages/Invoiced/oauth1-xero) + +#### Terminology (as per the RFC 5849 specification): + + client + An HTTP client (per [RFC2616]) capable of making OAuth- + authenticated requests (Section 3). + + server + An HTTP server (per [RFC2616]) capable of accepting OAuth- + authenticated requests (Section 3). + + protected resource + An access-restricted resource that can be obtained from the + server using an OAuth-authenticated request (Section 3). + + resource owner + An entity capable of accessing and controlling protected + resources by using credentials to authenticate with the server. + + credentials + Credentials are a pair of a unique identifier and a matching + shared secret. OAuth defines three classes of credentials: + client, temporary, and token, used to identify and authenticate + the client making the request, the authorization request, and + the access grant, respectively. + + token + A unique identifier issued by the server and used by the client + to associate authenticated requests with the resource owner + whose authorization is requested or has been obtained by the + client. Tokens have a matching shared-secret that is used by + the client to establish its ownership of the token, and its + authority to represent the resource owner. + + The original community specification used a somewhat different + terminology that maps to this specifications as follows (original + community terms provided on left): + + Consumer: client + + Service Provider: server + + User: resource owner + + Consumer Key and Secret: client credentials + + Request Token and Secret: temporary credentials + + Access Token and Secret: token credentials + + +## Install + +Via Composer + +```shell +$ composer require league/oauth1-client +``` + + +## Usage + +### Bitbucket + +```php +$server = new League\OAuth1\Client\Server\Bitbucket(array( + 'identifier' => 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => "http://your-callback-uri/", +)); +``` + +### Trello + +```php +$server = new League\OAuth1\Client\Server\Trello(array( + 'identifier' => 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => 'http://your-callback-uri/', + 'name' => 'your-application-name', // optional, defaults to null + 'expiration' => 'your-application-expiration', // optional ('never', '1day', '2days'), defaults to '1day' + 'scope' => 'your-application-scope' // optional ('read', 'read,write'), defaults to 'read' +)); +``` + +### Tumblr + +```php +$server = new League\OAuth1\Client\Server\Tumblr(array( + 'identifier' => 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => "http://your-callback-uri/", +)); +``` + +### Twitter + +```php +$server = new League\OAuth1\Client\Server\Twitter(array( + 'identifier' => 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => "http://your-callback-uri/", +)); +``` + +### Xing + +```php +$server = new League\OAuth1\Client\Server\Xing(array( + 'identifier' => 'your-consumer-key', + 'secret' => 'your-consumer-secret', + 'callback_uri' => "http://your-callback-uri/", +)); +``` + +### Showing a Login Button + +To begin, it's advisable that you include a login button on your website. Most servers (Twitter, Tumblr etc) have resources available for making buttons that are familiar to users. Some servers actually require you use their buttons as part of their terms. + +```html +Login With Twitter +``` + +### Retrieving Temporary Credentials + +The first step to authenticating with OAuth 1 is to retrieve temporary credentials. These have been referred to as **request tokens** in earlier versions of OAuth 1. + +To do this, we'll retrieve and store temporary credentials in the session, and redirect the user to the server: + +```php +// Retrieve temporary credentials +$temporaryCredentials = $server->getTemporaryCredentials(); + +// Store credentials in the session, we'll need them later +$_SESSION['temporary_credentials'] = serialize($temporaryCredentials); +session_write_close(); + +// Second part of OAuth 1.0 authentication is to redirect the +// resource owner to the login screen on the server. +$server->authorize($temporaryCredentials); +``` + +The user will be redirected to the familiar login screen on the server, where they will login to their account and authorise your app to access their data. + +### Retrieving Token Credentials + +Once the user has authenticated (or denied) your application, they will be redirected to the `callback_uri` which you specified when creating the server. + +> Note, some servers (such as Twitter) require that the callback URI you specify when authenticating matches what you registered with their app. This is to stop a potential third party impersonating you. This is actually part of the protocol however some servers choose to ignore this. +> +> Because of this, we actually require you specify a callback URI for all servers, regardless of whether the server requires it or not. This is good practice. + +You'll need to handle when the user is redirected back. This will involve retrieving token credentials, which you may then use to make calls to the server on behalf of the user. These have been referred to as **access tokens** in earlier versions of OAuth 1. + +```php +if (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { + // Retrieve the temporary credentials we saved before + $temporaryCredentials = unserialize($_SESSION['temporary_credentials']); + + // We will now retrieve token credentials from the server + $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $_GET['oauth_token'], $_GET['oauth_verifier']); +} +``` + +Now, you may choose to do what you need with the token credentials. You may store them in a database, in the session, or use them as one-off and then forget about them. + +All credentials, (`client credentials`, `temporary credentials` and `token credentials`) all implement `League\OAuth1\Client\Credentials\CredentialsInterface` and have two sets of setters and getters exposed: + +```php +var_dump($tokenCredentials->getIdentifier()); +var_dump($tokenCredentials->getSecret()); +``` + +In earlier versions of OAuth 1, the token credentials identifier and token credentials secret were referred to as **access token** and **access token secret**. Don't be scared by the new terminology here - they are the same. This package is using the exact terminology in the RFC 5849 OAuth 1 standard. + +> Twitter will send back an error message in the `denied` query string parameter, allowing you to provide feedback. Some servers do not send back an error message, but rather do not provide the successful `oauth_token` and `oauth_verifier` parameters. + +### Accessing User Information + +Now you have token credentials stored somewhere, you may use them to make calls against the server, as an authenticated user. + +While this package is not intended to be a wrapper for every server's API, it does include basic methods that you may use to retrieve limited information. An example of where this may be useful is if you are using social logins, you only need limited information to confirm who the user is. + +The four exposed methods are: + +```php +// User is an instance of League\OAuth1\Client\Server\User +$user = $server->getUserDetails($tokenCredentials); + +// UID is a string / integer unique representation of the user +$uid = $server->getUserUid($tokenCredentials); + +// Email is either a string or null (as some providers do not supply this data) +$email = $server->getUserEmail($tokenCredentials); + +// Screen name is also known as a username (Twitter handle etc) +$screenName = $server->getUserScreenName($tokenCredentials); +``` + +> `League\OAuth1\Client\Server\User` exposes a number of default public properties and also stores any additional data in an extra array - `$user->extra`. You may also iterate over a user's properties as if it was an array, `foreach ($user as $key => $value)`. + +## Examples + +Examples may be found under the [resources/examples](https://github.com/thephpleague/oauth1-client/tree/master/resources/examples) directory, which take the usage instructions here and go into a bit more depth. They are working examples that would only you substitute in your client credentials to have working. + +## Testing + +``` bash +$ phpunit +``` + + +## Contributing + +Please see [CONTRIBUTING](https://github.com/thephpleague/oauth1-client/blob/master/CONTRIBUTING.md) for details. + + +## Credits + +- [Ben Corlett](https://github.com/bencorlett) +- [Steven Maguire](https://github.com/stevenmaguire) +- [All Contributors](https://github.com/thephpleague/oauth1-client/contributors) + + +## License + +The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth1-client/blob/master/LICENSE) for more information. diff --git a/vendor/league/oauth1-client/composer.json b/vendor/league/oauth1-client/composer.json new file mode 100644 index 0000000000..c4e1fae195 --- /dev/null +++ b/vendor/league/oauth1-client/composer.json @@ -0,0 +1,46 @@ +{ + "name": "league/oauth1-client", + "description": "OAuth 1.0 Client Library", + "license": "MIT", + "require": { + "php": ">=5.5.0", + "guzzlehttp/guzzle": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "mockery/mockery": "^0.9", + "squizlabs/php_codesniffer": "^2.0" + }, + "keywords": [ + "oauth", + "oauth1", + "authorization", + "authentication", + "idp", + "identity", + "sso", + "single sign on", + "bitbucket", + "trello", + "tumblr", + "twitter" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "League\\OAuth1\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } +} diff --git a/vendor/league/oauth1-client/phpunit.xml b/vendor/league/oauth1-client/phpunit.xml new file mode 100644 index 0000000000..d423c1d6e7 --- /dev/null +++ b/vendor/league/oauth1-client/phpunit.xml @@ -0,0 +1,28 @@ + + + + + + + + + tests + + + + + ./src/ + + + diff --git a/vendor/league/oauth1-client/resources/examples/tumblr.php b/vendor/league/oauth1-client/resources/examples/tumblr.php new file mode 100644 index 0000000000..236914a7df --- /dev/null +++ b/vendor/league/oauth1-client/resources/examples/tumblr.php @@ -0,0 +1,87 @@ + 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => "http://your-callback-uri/", +)); + +// Start session +session_start(); + +// Step 4 +if (isset($_GET['user'])) { + + // Check somebody hasn't manually entered this URL in, + // by checking that we have the token credentials in + // the session. + if ( ! isset($_SESSION['token_credentials'])) { + echo 'No token credentials.'; + exit(1); + } + + // Retrieve our token credentials. From here, it's play time! + $tokenCredentials = unserialize($_SESSION['token_credentials']); + + // // Below is an example of retrieving the identifier & secret + // // (formally known as access token key & secret in earlier + // // OAuth 1.0 specs). + // $identifier = $tokenCredentials->getIdentifier(); + // $secret = $tokenCredentials->getSecret(); + + // Some OAuth clients try to act as an API wrapper for + // the server and it's API. We don't. This is what you + // get - the ability to access basic information. If + // you want to get fancy, you should be grabbing a + // package for interacting with the APIs, by using + // the identifier & secret that this package was + // designed to retrieve for you. But, for fun, + // here's basic user information. + $user = $server->getUserDetails($tokenCredentials); + var_dump($user); + +// Step 3 +} elseif (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { + + // Retrieve the temporary credentials from step 2 + $temporaryCredentials = unserialize($_SESSION['temporary_credentials']); + + // Third and final part to OAuth 1.0 authentication is to retrieve token + // credentials (formally known as access tokens in earlier OAuth 1.0 + // specs). + $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $_GET['oauth_token'], $_GET['oauth_verifier']); + + // Now, we'll store the token credentials and discard the temporary + // ones - they're irrelevant at this stage. + unset($_SESSION['temporary_credentials']); + $_SESSION['token_credentials'] = serialize($tokenCredentials); + session_write_close(); + + // Redirect to the user page + header("Location: http://{$_SERVER['HTTP_HOST']}/?user=user"); + exit; + +// Step 2 +} elseif (isset($_GET['go'])) { + + // First part of OAuth 1.0 authentication is retrieving temporary credentials. + // These identify you as a client to the server. + $temporaryCredentials = $server->getTemporaryCredentials(); + + // Store the credentials in the session. + $_SESSION['temporary_credentials'] = serialize($temporaryCredentials); + session_write_close(); + + // Second part of OAuth 1.0 authentication is to redirect the + // resource owner to the login screen on the server. + $server->authorize($temporaryCredentials); + +// Step 1 +} else { + + // Display link to start process + echo 'Login'; +} diff --git a/vendor/league/oauth1-client/resources/examples/twitter.php b/vendor/league/oauth1-client/resources/examples/twitter.php new file mode 100644 index 0000000000..676def2b44 --- /dev/null +++ b/vendor/league/oauth1-client/resources/examples/twitter.php @@ -0,0 +1,91 @@ + 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => "http://your-callback-uri/", +)); + +// Start session +session_start(); + +// Step 4 +if (isset($_GET['user'])) { + + // Check somebody hasn't manually entered this URL in, + // by checking that we have the token credentials in + // the session. + if ( ! isset($_SESSION['token_credentials'])) { + echo 'No token credentials.'; + exit(1); + } + + // Retrieve our token credentials. From here, it's play time! + $tokenCredentials = unserialize($_SESSION['token_credentials']); + + // // Below is an example of retrieving the identifier & secret + // // (formally known as access token key & secret in earlier + // // OAuth 1.0 specs). + // $identifier = $tokenCredentials->getIdentifier(); + // $secret = $tokenCredentials->getSecret(); + + // Some OAuth clients try to act as an API wrapper for + // the server and it's API. We don't. This is what you + // get - the ability to access basic information. If + // you want to get fancy, you should be grabbing a + // package for interacting with the APIs, by using + // the identifier & secret that this package was + // designed to retrieve for you. But, for fun, + // here's basic user information. + $user = $server->getUserDetails($tokenCredentials); + var_dump($user); + +// Step 3 +} elseif (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { + + // Retrieve the temporary credentials from step 2 + $temporaryCredentials = unserialize($_SESSION['temporary_credentials']); + + // Third and final part to OAuth 1.0 authentication is to retrieve token + // credentials (formally known as access tokens in earlier OAuth 1.0 + // specs). + $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $_GET['oauth_token'], $_GET['oauth_verifier']); + + // Now, we'll store the token credentials and discard the temporary + // ones - they're irrelevant at this stage. + unset($_SESSION['temporary_credentials']); + $_SESSION['token_credentials'] = serialize($tokenCredentials); + session_write_close(); + + // Redirect to the user page + header("Location: http://{$_SERVER['HTTP_HOST']}/?user=user"); + exit; + +// Step 2.5 - denied request to authorize client +} elseif (isset($_GET['denied'])) { + echo 'Hey! You denied the client access to your Twitter account! If you did this by mistake, you should try again.'; + +// Step 2 +} elseif (isset($_GET['go'])) { + + // First part of OAuth 1.0 authentication is retrieving temporary credentials. + // These identify you as a client to the server. + $temporaryCredentials = $server->getTemporaryCredentials(); + + // Store the credentials in the session. + $_SESSION['temporary_credentials'] = serialize($temporaryCredentials); + session_write_close(); + + // Second part of OAuth 1.0 authentication is to redirect the + // resource owner to the login screen on the server. + $server->authorize($temporaryCredentials); + +// Step 1 +} else { + + // Display link to start process + echo 'Login'; +} diff --git a/vendor/league/oauth1-client/resources/examples/xing.php b/vendor/league/oauth1-client/resources/examples/xing.php new file mode 100644 index 0000000000..2ac0bd435f --- /dev/null +++ b/vendor/league/oauth1-client/resources/examples/xing.php @@ -0,0 +1,91 @@ + 'your-identifier', + 'secret' => 'your-secret', + 'callback_uri' => "http://your-callback-uri/", +)); + +// Start session +session_start(); + +// Step 4 +if (isset($_GET['user'])) { + + // Check somebody hasn't manually entered this URL in, + // by checking that we have the token credentials in + // the session. + if ( ! isset($_SESSION['token_credentials'])) { + echo 'No token credentials.'; + exit(1); + } + + // Retrieve our token credentials. From here, it's play time! + $tokenCredentials = unserialize($_SESSION['token_credentials']); + + // // Below is an example of retrieving the identifier & secret + // // (formally known as access token key & secret in earlier + // // OAuth 1.0 specs). + // $identifier = $tokenCredentials->getIdentifier(); + // $secret = $tokenCredentials->getSecret(); + + // Some OAuth clients try to act as an API wrapper for + // the server and it's API. We don't. This is what you + // get - the ability to access basic information. If + // you want to get fancy, you should be grabbing a + // package for interacting with the APIs, by using + // the identifier & secret that this package was + // designed to retrieve for you. But, for fun, + // here's basic user information. + $user = $server->getUserDetails($tokenCredentials); + var_dump($user); + +// Step 3 +} elseif (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) { + + // Retrieve the temporary credentials from step 2 + $temporaryCredentials = unserialize($_SESSION['temporary_credentials']); + + // Third and final part to OAuth 1.0 authentication is to retrieve token + // credentials (formally known as access tokens in earlier OAuth 1.0 + // specs). + $tokenCredentials = $server->getTokenCredentials($temporaryCredentials, $_GET['oauth_token'], $_GET['oauth_verifier']); + + // Now, we'll store the token credentials and discard the temporary + // ones - they're irrelevant at this stage. + unset($_SESSION['temporary_credentials']); + $_SESSION['token_credentials'] = serialize($tokenCredentials); + session_write_close(); + + // Redirect to the user page + header("Location: http://{$_SERVER['HTTP_HOST']}/?user=user"); + exit; + +// Step 2.5 - denied request to authorize client +} elseif (isset($_GET['denied'])) { + echo 'Hey! You denied the client access to your Xing account! If you did this by mistake, you should try again.'; + +// Step 2 +} elseif (isset($_GET['go'])) { + + // First part of OAuth 1.0 authentication is retrieving temporary credentials. + // These identify you as a client to the server. + $temporaryCredentials = $server->getTemporaryCredentials(); + + // Store the credentials in the session. + $_SESSION['temporary_credentials'] = serialize($temporaryCredentials); + session_write_close(); + + // Second part of OAuth 1.0 authentication is to redirect the + // resource owner to the login screen on the server. + $server->authorize($temporaryCredentials); + +// Step 1 +} else { + + // Display link to start process + echo 'Login'; +} diff --git a/vendor/league/oauth1-client/rfc5849.txt b/vendor/league/oauth1-client/rfc5849.txt new file mode 100644 index 0000000000..636814d8d2 --- /dev/null +++ b/vendor/league/oauth1-client/rfc5849.txt @@ -0,0 +1,2131 @@ + + + + + + +Internet Engineering Task Force (IETF) E. Hammer-Lahav, Ed. +Request for Comments: 5849 April 2010 +Category: Informational +ISSN: 2070-1721 + + + The OAuth 1.0 Protocol + +Abstract + + OAuth provides a method for clients to access server resources on + behalf of a resource owner (such as a different client or an end- + user). It also provides a process for end-users to authorize third- + party access to their server resources without sharing their + credentials (typically, a username and password pair), using user- + agent redirections. + +Status of This Memo + + This document is not an Internet Standards Track specification; it is + published for informational purposes. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Not all documents + approved by the IESG are a candidate for any level of Internet + Standard; see Section 2 of RFC 5741. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc5849. + +Copyright Notice + + Copyright (c) 2010 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + + + +Hammer-Lahav Informational [Page 1] + +RFC 5849 OAuth 1.0 April 2010 + + +Table of Contents + + 1. Introduction ....................................................3 + 1.1. Terminology ................................................4 + 1.2. Example ....................................................5 + 1.3. Notational Conventions .....................................7 + 2. Redirection-Based Authorization .................................8 + 2.1. Temporary Credentials ......................................9 + 2.2. Resource Owner Authorization ..............................10 + 2.3. Token Credentials .........................................12 + 3. Authenticated Requests .........................................14 + 3.1. Making Requests ...........................................14 + 3.2. Verifying Requests ........................................16 + 3.3. Nonce and Timestamp .......................................17 + 3.4. Signature .................................................18 + 3.4.1. Signature Base String ..............................18 + 3.4.2. HMAC-SHA1 ..........................................25 + 3.4.3. RSA-SHA1 ...........................................25 + 3.4.4. PLAINTEXT ..........................................26 + 3.5. Parameter Transmission ....................................26 + 3.5.1. Authorization Header ...............................27 + 3.5.2. Form-Encoded Body ..................................28 + 3.5.3. Request URI Query ..................................28 + 3.6. Percent Encoding ..........................................29 + 4. Security Considerations ........................................29 + 4.1. RSA-SHA1 Signature Method .................................29 + 4.2. Confidentiality of Requests ...............................30 + 4.3. Spoofing by Counterfeit Servers ...........................30 + 4.4. Proxying and Caching of Authenticated Content .............30 + 4.5. Plaintext Storage of Credentials ..........................30 + 4.6. Secrecy of the Client Credentials .........................31 + 4.7. Phishing Attacks ..........................................31 + 4.8. Scoping of Access Requests ................................31 + 4.9. Entropy of Secrets ........................................32 + 4.10. Denial-of-Service / Resource-Exhaustion Attacks ..........32 + 4.11. SHA-1 Cryptographic Attacks ..............................33 + 4.12. Signature Base String Limitations ........................33 + 4.13. Cross-Site Request Forgery (CSRF) ........................33 + 4.14. User Interface Redress ...................................34 + 4.15. Automatic Processing of Repeat Authorizations ............34 + 5. Acknowledgments ................................................35 + Appendix A. Differences from the Community Edition ...............36 + 6. References .....................................................37 + 6.1. Normative References ......................................37 + 6.2. Informative References ....................................38 + + + + + + +Hammer-Lahav Informational [Page 2] + +RFC 5849 OAuth 1.0 April 2010 + + +1. Introduction + + The OAuth protocol was originally created by a small community of web + developers from a variety of websites and other Internet services who + wanted to solve the common problem of enabling delegated access to + protected resources. The resulting OAuth protocol was stabilized at + version 1.0 in October 2007, and revised in June 2009 (Revision A) as + published at . + + This specification provides an informational documentation of OAuth + Core 1.0 Revision A, addresses several errata reported since that + time, and makes numerous editorial clarifications. While this + specification is not an item of the IETF's OAuth Working Group, which + at the time of writing is working on an OAuth version that can be + appropriate for publication on the standards track, it has been + transferred to the IETF for change control by authors of the original + work. + + In the traditional client-server authentication model, the client + uses its credentials to access its resources hosted by the server. + With the increasing use of distributed web services and cloud + computing, third-party applications require access to these server- + hosted resources. + + OAuth introduces a third role to the traditional client-server + authentication model: the resource owner. In the OAuth model, the + client (which is not the resource owner, but is acting on its behalf) + requests access to resources controlled by the resource owner, but + hosted by the server. In addition, OAuth allows the server to verify + not only the resource owner authorization, but also the identity of + the client making the request. + + OAuth provides a method for clients to access server resources on + behalf of a resource owner (such as a different client or an end- + user). It also provides a process for end-users to authorize third- + party access to their server resources without sharing their + credentials (typically, a username and password pair), using user- + agent redirections. + + For example, a web user (resource owner) can grant a printing service + (client) access to her private photos stored at a photo sharing + service (server), without sharing her username and password with the + printing service. Instead, she authenticates directly with the photo + sharing service which issues the printing service delegation-specific + credentials. + + + + + + +Hammer-Lahav Informational [Page 3] + +RFC 5849 OAuth 1.0 April 2010 + + + In order for the client to access resources, it first has to obtain + permission from the resource owner. This permission is expressed in + the form of a token and matching shared-secret. The purpose of the + token is to make it unnecessary for the resource owner to share its + credentials with the client. Unlike the resource owner credentials, + tokens can be issued with a restricted scope and limited lifetime, + and revoked independently. + + This specification consists of two parts. The first part defines a + redirection-based user-agent process for end-users to authorize + client access to their resources, by authenticating directly with the + server and provisioning tokens to the client for use with the + authentication method. The second part defines a method for making + authenticated HTTP [RFC2616] requests using two sets of credentials, + one identifying the client making the request, and a second + identifying the resource owner on whose behalf the request is being + made. + + The use of OAuth with any transport protocol other than [RFC2616] is + undefined. + +1.1. Terminology + + client + An HTTP client (per [RFC2616]) capable of making OAuth- + authenticated requests (Section 3). + + server + An HTTP server (per [RFC2616]) capable of accepting OAuth- + authenticated requests (Section 3). + + protected resource + An access-restricted resource that can be obtained from the + server using an OAuth-authenticated request (Section 3). + + resource owner + An entity capable of accessing and controlling protected + resources by using credentials to authenticate with the server. + + credentials + Credentials are a pair of a unique identifier and a matching + shared secret. OAuth defines three classes of credentials: + client, temporary, and token, used to identify and authenticate + the client making the request, the authorization request, and + the access grant, respectively. + + + + + + +Hammer-Lahav Informational [Page 4] + +RFC 5849 OAuth 1.0 April 2010 + + + token + A unique identifier issued by the server and used by the client + to associate authenticated requests with the resource owner + whose authorization is requested or has been obtained by the + client. Tokens have a matching shared-secret that is used by + the client to establish its ownership of the token, and its + authority to represent the resource owner. + + The original community specification used a somewhat different + terminology that maps to this specifications as follows (original + community terms provided on left): + + Consumer: client + + Service Provider: server + + User: resource owner + + Consumer Key and Secret: client credentials + + Request Token and Secret: temporary credentials + + Access Token and Secret: token credentials + +1.2. Example + + Jane (resource owner) has recently uploaded some private vacation + photos (protected resources) to her photo sharing site + 'photos.example.net' (server). She would like to use the + 'printer.example.com' website (client) to print one of these photos. + Typically, Jane signs into 'photos.example.net' using her username + and password. + + However, Jane does not wish to share her username and password with + the 'printer.example.com' website, which needs to access the photo in + order to print it. In order to provide its users with better + service, 'printer.example.com' has signed up for a set of + 'photos.example.net' client credentials ahead of time: + + Client Identifier + dpf43f3p2l4k3l03 + + Client Shared-Secret: + kd94hf93k423kf44 + + The 'printer.example.com' website has also configured its application + to use the protocol endpoints listed in the 'photos.example.net' API + documentation, which use the "HMAC-SHA1" signature method: + + + +Hammer-Lahav Informational [Page 5] + +RFC 5849 OAuth 1.0 April 2010 + + + Temporary Credential Request + https://photos.example.net/initiate + + Resource Owner Authorization URI: + https://photos.example.net/authorize + + Token Request URI: + https://photos.example.net/token + + Before 'printer.example.com' can ask Jane to grant it access to the + photos, it must first establish a set of temporary credentials with + 'photos.example.net' to identify the delegation request. To do so, + the client sends the following HTTPS [RFC2818] request to the server: + + POST /initiate HTTP/1.1 + Host: photos.example.net + Authorization: OAuth realm="Photos", + oauth_consumer_key="dpf43f3p2l4k3l03", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131200", + oauth_nonce="wIjqoS", + oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready", + oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D" + + The server validates the request and replies with a set of temporary + credentials in the body of the HTTP response (line breaks are for + display purposes only): + + HTTP/1.1 200 OK + Content-Type: application/x-www-form-urlencoded + + oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03& + oauth_callback_confirmed=true + + The client redirects Jane's user-agent to the server's Resource Owner + Authorization endpoint to obtain Jane's approval for accessing her + private photos: + + https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola + + The server requests Jane to sign in using her username and password + and if successful, asks her to approve granting 'printer.example.com' + access to her private photos. Jane approves the request and her + user-agent is redirected to the callback URI provided by the client + in the previous request (line breaks are for display purposes only): + + http://printer.example.com/ready? + oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884 + + + +Hammer-Lahav Informational [Page 6] + +RFC 5849 OAuth 1.0 April 2010 + + + The callback request informs the client that Jane completed the + authorization process. The client then requests a set of token + credentials using its temporary credentials (over a secure Transport + Layer Security (TLS) channel): + + POST /token HTTP/1.1 + Host: photos.example.net + Authorization: OAuth realm="Photos", + oauth_consumer_key="dpf43f3p2l4k3l03", + oauth_token="hh5s93j4hdidpola", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="walatlh", + oauth_verifier="hfdp7dh39dks9884", + oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D" + + The server validates the request and replies with a set of token + credentials in the body of the HTTP response: + + HTTP/1.1 200 OK + Content-Type: application/x-www-form-urlencoded + + oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00 + + With a set of token credentials, the client is now ready to request + the private photo: + + GET /photos?file=vacation.jpg&size=original HTTP/1.1 + Host: photos.example.net + Authorization: OAuth realm="Photos", + oauth_consumer_key="dpf43f3p2l4k3l03", + oauth_token="nnch734d00sl2jdk", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131202", + oauth_nonce="chapoH", + oauth_signature="MdpQcU8iPSUjWoN%2FUDMsK2sui9I%3D" + + The 'photos.example.net' server validates the request and responds + with the requested photo. 'printer.example.com' is able to continue + accessing Jane's private photos using the same set of token + credentials for the duration of Jane's authorization, or until Jane + revokes access. + +1.3. Notational Conventions + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + + +Hammer-Lahav Informational [Page 7] + +RFC 5849 OAuth 1.0 April 2010 + + +2. Redirection-Based Authorization + + OAuth uses tokens to represent the authorization granted to the + client by the resource owner. Typically, token credentials are + issued by the server at the resource owner's request, after + authenticating the resource owner's identity (usually using a + username and password). + + There are many ways in which a server can facilitate the provisioning + of token credentials. This section defines one such way, using HTTP + redirections and the resource owner's user-agent. This redirection- + based authorization method includes three steps: + + 1. The client obtains a set of temporary credentials from the server + (in the form of an identifier and shared-secret). The temporary + credentials are used to identify the access request throughout + the authorization process. + + 2. The resource owner authorizes the server to grant the client's + access request (identified by the temporary credentials). + + 3. The client uses the temporary credentials to request a set of + token credentials from the server, which will enable it to access + the resource owner's protected resources. + + The server MUST revoke the temporary credentials after being used + once to obtain the token credentials. It is RECOMMENDED that the + temporary credentials have a limited lifetime. Servers SHOULD enable + resource owners to revoke token credentials after they have been + issued to clients. + + In order for the client to perform these steps, the server needs to + advertise the URIs of the following three endpoints: + + Temporary Credential Request + The endpoint used by the client to obtain a set of temporary + credentials as described in Section 2.1. + + Resource Owner Authorization + The endpoint to which the resource owner is redirected to grant + authorization as described in Section 2.2. + + Token Request + The endpoint used by the client to request a set of token + credentials using the set of temporary credentials as described + in Section 2.3. + + + + + +Hammer-Lahav Informational [Page 8] + +RFC 5849 OAuth 1.0 April 2010 + + + The three URIs advertised by the server MAY include a query component + as defined by [RFC3986], Section 3, but if present, the query MUST + NOT contain any parameters beginning with the "oauth_" prefix, to + avoid conflicts with the protocol parameters added to the URIs when + used. + + The methods in which the server advertises and documents its three + endpoints are beyond the scope of this specification. Clients should + avoid making assumptions about the size of tokens and other server- + generated values, which are left undefined by this specification. In + addition, protocol parameters MAY include values that require + encoding when transmitted. Clients and servers should not make + assumptions about the possible range of their values. + +2.1. Temporary Credentials + + The client obtains a set of temporary credentials from the server by + making an authenticated (Section 3) HTTP "POST" request to the + Temporary Credential Request endpoint (unless the server advertises + another HTTP request method for the client to use). The client + constructs a request URI by adding the following REQUIRED parameter + to the request (in addition to the other protocol parameters, using + the same parameter transmission method): + + oauth_callback: An absolute URI back to which the server will + redirect the resource owner when the Resource Owner + Authorization step (Section 2.2) is completed. If + the client is unable to receive callbacks or a + callback URI has been established via other means, + the parameter value MUST be set to "oob" (case + sensitive), to indicate an out-of-band + configuration. + + Servers MAY specify additional parameters. + + When making the request, the client authenticates using only the + client credentials. The client MAY omit the empty "oauth_token" + protocol parameter from the request and MUST use the empty string as + the token secret value. + + Since the request results in the transmission of plain text + credentials in the HTTP response, the server MUST require the use of + a transport-layer mechanisms such as TLS or Secure Socket Layer (SSL) + (or a secure channel with equivalent protections). + + + + + + + +Hammer-Lahav Informational [Page 9] + +RFC 5849 OAuth 1.0 April 2010 + + + For example, the client makes the following HTTPS request: + + POST /request_temp_credentials HTTP/1.1 + Host: server.example.com + Authorization: OAuth realm="Example", + oauth_consumer_key="jd83jd92dhsh93js", + oauth_signature_method="PLAINTEXT", + oauth_callback="http%3A%2F%2Fclient.example.net%2Fcb%3Fx%3D1", + oauth_signature="ja893SD9%26" + + The server MUST verify (Section 3.2) the request and if valid, + respond back to the client with a set of temporary credentials (in + the form of an identifier and shared-secret). The temporary + credentials are included in the HTTP response body using the + "application/x-www-form-urlencoded" content type as defined by + [W3C.REC-html40-19980424] with a 200 status code (OK). + + The response contains the following REQUIRED parameters: + + oauth_token + The temporary credentials identifier. + + oauth_token_secret + The temporary credentials shared-secret. + + oauth_callback_confirmed + MUST be present and set to "true". The parameter is used to + differentiate from previous versions of the protocol. + + Note that even though the parameter names include the term 'token', + these credentials are not token credentials, but are used in the next + two steps in a similar manner to token credentials. + + For example (line breaks are for display purposes only): + + HTTP/1.1 200 OK + Content-Type: application/x-www-form-urlencoded + + oauth_token=hdk48Djdsa&oauth_token_secret=xyz4992k83j47x0b& + oauth_callback_confirmed=true + +2.2. Resource Owner Authorization + + Before the client requests a set of token credentials from the + server, it MUST send the user to the server to authorize the request. + The client constructs a request URI by adding the following REQUIRED + query parameter to the Resource Owner Authorization endpoint URI: + + + + +Hammer-Lahav Informational [Page 10] + +RFC 5849 OAuth 1.0 April 2010 + + + oauth_token + The temporary credentials identifier obtained in Section 2.1 in + the "oauth_token" parameter. Servers MAY declare this + parameter as OPTIONAL, in which case they MUST provide a way + for the resource owner to indicate the identifier through other + means. + + Servers MAY specify additional parameters. + + The client directs the resource owner to the constructed URI using an + HTTP redirection response, or by other means available to it via the + resource owner's user-agent. The request MUST use the HTTP "GET" + method. + + For example, the client redirects the resource owner's user-agent to + make the following HTTPS request: + + GET /authorize_access?oauth_token=hdk48Djdsa HTTP/1.1 + Host: server.example.com + + The way in which the server handles the authorization request, + including whether it uses a secure channel such as TLS/SSL is beyond + the scope of this specification. However, the server MUST first + verify the identity of the resource owner. + + When asking the resource owner to authorize the requested access, the + server SHOULD present to the resource owner information about the + client requesting access based on the association of the temporary + credentials with the client identity. When displaying any such + information, the server SHOULD indicate if the information has been + verified. + + After receiving an authorization decision from the resource owner, + the server redirects the resource owner to the callback URI if one + was provided in the "oauth_callback" parameter or by other means. + + To make sure that the resource owner granting access is the same + resource owner returning back to the client to complete the process, + the server MUST generate a verification code: an unguessable value + passed to the client via the resource owner and REQUIRED to complete + the process. The server constructs the request URI by adding the + following REQUIRED parameters to the callback URI query component: + + oauth_token + The temporary credentials identifier received from the client. + + + + + + +Hammer-Lahav Informational [Page 11] + +RFC 5849 OAuth 1.0 April 2010 + + + oauth_verifier + The verification code. + + If the callback URI already includes a query component, the server + MUST append the OAuth parameters to the end of the existing query. + + For example, the server redirects the resource owner's user-agent to + make the following HTTP request: + + GET /cb?x=1&oauth_token=hdk48Djdsa&oauth_verifier=473f82d3 HTTP/1.1 + Host: client.example.net + + If the client did not provide a callback URI, the server SHOULD + display the value of the verification code, and instruct the resource + owner to manually inform the client that authorization is completed. + If the server knows a client to be running on a limited device, it + SHOULD ensure that the verifier value is suitable for manual entry. + +2.3. Token Credentials + + The client obtains a set of token credentials from the server by + making an authenticated (Section 3) HTTP "POST" request to the Token + Request endpoint (unless the server advertises another HTTP request + method for the client to use). The client constructs a request URI + by adding the following REQUIRED parameter to the request (in + addition to the other protocol parameters, using the same parameter + transmission method): + + oauth_verifier + The verification code received from the server in the previous + step. + + When making the request, the client authenticates using the client + credentials as well as the temporary credentials. The temporary + credentials are used as a substitute for token credentials in the + authenticated request and transmitted using the "oauth_token" + parameter. + + Since the request results in the transmission of plain text + credentials in the HTTP response, the server MUST require the use of + a transport-layer mechanism such as TLS or SSL (or a secure channel + with equivalent protections). + + + + + + + + + +Hammer-Lahav Informational [Page 12] + +RFC 5849 OAuth 1.0 April 2010 + + + For example, the client makes the following HTTPS request: + + POST /request_token HTTP/1.1 + Host: server.example.com + Authorization: OAuth realm="Example", + oauth_consumer_key="jd83jd92dhsh93js", + oauth_token="hdk48Djdsa", + oauth_signature_method="PLAINTEXT", + oauth_verifier="473f82d3", + oauth_signature="ja893SD9%26xyz4992k83j47x0b" + + The server MUST verify (Section 3.2) the validity of the request, + ensure that the resource owner has authorized the provisioning of + token credentials to the client, and ensure that the temporary + credentials have not expired or been used before. The server MUST + also verify the verification code received from the client. If the + request is valid and authorized, the token credentials are included + in the HTTP response body using the + "application/x-www-form-urlencoded" content type as defined by + [W3C.REC-html40-19980424] with a 200 status code (OK). + + The response contains the following REQUIRED parameters: + + oauth_token + The token identifier. + + oauth_token_secret + The token shared-secret. + + For example: + + HTTP/1.1 200 OK + Content-Type: application/x-www-form-urlencoded + + oauth_token=j49ddk933skd9dks&oauth_token_secret=ll399dj47dskfjdk + + The server must retain the scope, duration, and other attributes + approved by the resource owner, and enforce these restrictions when + receiving a client request made with the token credentials issued. + + Once the client receives and stores the token credentials, it can + proceed to access protected resources on behalf of the resource owner + by making authenticated requests (Section 3) using the client + credentials together with the token credentials received. + + + + + + + +Hammer-Lahav Informational [Page 13] + +RFC 5849 OAuth 1.0 April 2010 + + +3. Authenticated Requests + + The HTTP authentication methods defined by [RFC2617] enable clients + to make authenticated HTTP requests. Clients using these methods + gain access to protected resources by using their credentials + (typically, a username and password pair), which allow the server to + verify their authenticity. Using these methods for delegation + requires the client to assume the role of the resource owner. + + OAuth provides a method designed to include two sets of credentials + with each request, one to identify the client, and another to + identify the resource owner. Before a client can make authenticated + requests on behalf of the resource owner, it must obtain a token + authorized by the resource owner. Section 2 provides one such method + through which the client can obtain a token authorized by the + resource owner. + + The client credentials take the form of a unique identifier and an + associated shared-secret or RSA key pair. Prior to making + authenticated requests, the client establishes a set of credentials + with the server. The process and requirements for provisioning these + are outside the scope of this specification. Implementers are urged + to consider the security ramifications of using client credentials, + some of which are described in Section 4.6. + + Making authenticated requests requires prior knowledge of the + server's configuration. OAuth includes multiple methods for + transmitting protocol parameters with requests (Section 3.5), as well + as multiple methods for the client to prove its rightful ownership of + the credentials used (Section 3.4). The way in which clients + discover the required configuration is outside the scope of this + specification. + +3.1. Making Requests + + An authenticated request includes several protocol parameters. Each + parameter name begins with the "oauth_" prefix, and the parameter + names and values are case sensitive. Clients make authenticated + requests by calculating the values of a set of protocol parameters + and adding them to the HTTP request as follows: + + 1. The client assigns value to each of these REQUIRED (unless + specified otherwise) protocol parameters: + + + + + + + + +Hammer-Lahav Informational [Page 14] + +RFC 5849 OAuth 1.0 April 2010 + + + oauth_consumer_key + The identifier portion of the client credentials (equivalent to + a username). The parameter name reflects a deprecated term + (Consumer Key) used in previous revisions of the specification, + and has been retained to maintain backward compatibility. + + oauth_token + The token value used to associate the request with the resource + owner. If the request is not associated with a resource owner + (no token available), clients MAY omit the parameter. + + oauth_signature_method + The name of the signature method used by the client to sign the + request, as defined in Section 3.4. + + oauth_timestamp + The timestamp value as defined in Section 3.3. The parameter + MAY be omitted when using the "PLAINTEXT" signature method. + + oauth_nonce + The nonce value as defined in Section 3.3. The parameter MAY + be omitted when using the "PLAINTEXT" signature method. + + oauth_version + OPTIONAL. If present, MUST be set to "1.0". Provides the + version of the authentication process as defined in this + specification. + + 2. The protocol parameters are added to the request using one of the + transmission methods listed in Section 3.5. Each parameter MUST + NOT appear more than once per request. + + 3. The client calculates and assigns the value of the + "oauth_signature" parameter as described in Section 3.4 and adds + the parameter to the request using the same method as in the + previous step. + + 4. The client sends the authenticated HTTP request to the server. + + For example, to make the following HTTP request authenticated (the + "c2&a3=2+q" string in the following examples is used to illustrate + the impact of a form-encoded entity-body): + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + + c2&a3=2+q + + + +Hammer-Lahav Informational [Page 15] + +RFC 5849 OAuth 1.0 April 2010 + + + The client assigns values to the following protocol parameters using + its client credentials, token credentials, the current timestamp, a + uniquely generated nonce, and indicates that it will use the + "HMAC-SHA1" signature method: + + oauth_consumer_key: 9djdj82h48djs9d2 + oauth_token: kkk9d7dh3k39sjv7 + oauth_signature_method: HMAC-SHA1 + oauth_timestamp: 137131201 + oauth_nonce: 7d8f3e4a + + The client adds the protocol parameters to the request using the + OAuth HTTP "Authorization" header field: + + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a" + + Then, it calculates the value of the "oauth_signature" parameter + (using client secret "j49sk3j29djd" and token secret "dh893hdasih9"), + adds it to the request, and sends the HTTP request to the server: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" + + c2&a3=2+q + +3.2. Verifying Requests + + Servers receiving an authenticated request MUST validate it by: + + o Recalculating the request signature independently as described in + Section 3.4 and comparing it to the value received from the client + via the "oauth_signature" parameter. + + + + + + +Hammer-Lahav Informational [Page 16] + +RFC 5849 OAuth 1.0 April 2010 + + + o If using the "HMAC-SHA1" or "RSA-SHA1" signature methods, ensuring + that the combination of nonce/timestamp/token (if present) + received from the client has not been used before in a previous + request (the server MAY reject requests with stale timestamps as + described in Section 3.3). + + o If a token is present, verifying the scope and status of the + client authorization as represented by the token (the server MAY + choose to restrict token usage to the client to which it was + issued). + + o If the "oauth_version" parameter is present, ensuring its value is + "1.0". + + If the request fails verification, the server SHOULD respond with the + appropriate HTTP response status code. The server MAY include + further details about why the request was rejected in the response + body. + + The server SHOULD return a 400 (Bad Request) status code when + receiving a request with unsupported parameters, an unsupported + signature method, missing parameters, or duplicated protocol + parameters. The server SHOULD return a 401 (Unauthorized) status + code when receiving a request with invalid client credentials, an + invalid or expired token, an invalid signature, or an invalid or used + nonce. + +3.3. Nonce and Timestamp + + The timestamp value MUST be a positive integer. Unless otherwise + specified by the server's documentation, the timestamp is expressed + in the number of seconds since January 1, 1970 00:00:00 GMT. + + A nonce is a random string, uniquely generated by the client to allow + the server to verify that a request has never been made before and + helps prevent replay attacks when requests are made over a non-secure + channel. The nonce value MUST be unique across all requests with the + same timestamp, client credentials, and token combinations. + + To avoid the need to retain an infinite number of nonce values for + future checks, servers MAY choose to restrict the time period after + which a request with an old timestamp is rejected. Note that this + restriction implies a level of synchronization between the client's + and server's clocks. Servers applying such a restriction MAY provide + a way for the client to sync with the server's clock; alternatively, + both systems could synchronize with a trusted time service. Details + of clock synchronization strategies are beyond the scope of this + specification. + + + +Hammer-Lahav Informational [Page 17] + +RFC 5849 OAuth 1.0 April 2010 + + +3.4. Signature + + OAuth-authenticated requests can have two sets of credentials: those + passed via the "oauth_consumer_key" parameter and those in the + "oauth_token" parameter. In order for the server to verify the + authenticity of the request and prevent unauthorized access, the + client needs to prove that it is the rightful owner of the + credentials. This is accomplished using the shared-secret (or RSA + key) part of each set of credentials. + + OAuth provides three methods for the client to prove its rightful + ownership of the credentials: "HMAC-SHA1", "RSA-SHA1", and + "PLAINTEXT". These methods are generally referred to as signature + methods, even though "PLAINTEXT" does not involve a signature. In + addition, "RSA-SHA1" utilizes an RSA key instead of the shared- + secrets associated with the client credentials. + + OAuth does not mandate a particular signature method, as each + implementation can have its own unique requirements. Servers are + free to implement and document their own custom methods. + Recommending any particular method is beyond the scope of this + specification. Implementers should review the Security + Considerations section (Section 4) before deciding on which method to + support. + + The client declares which signature method is used via the + "oauth_signature_method" parameter. It then generates a signature + (or a string of an equivalent value) and includes it in the + "oauth_signature" parameter. The server verifies the signature as + specified for each method. + + The signature process does not change the request or its parameters, + with the exception of the "oauth_signature" parameter. + +3.4.1. Signature Base String + + The signature base string is a consistent, reproducible concatenation + of several of the HTTP request elements into a single string. The + string is used as an input to the "HMAC-SHA1" and "RSA-SHA1" + signature methods. + + The signature base string includes the following components of the + HTTP request: + + o The HTTP request method (e.g., "GET", "POST", etc.). + + o The authority as declared by the HTTP "Host" request header field. + + + + +Hammer-Lahav Informational [Page 18] + +RFC 5849 OAuth 1.0 April 2010 + + + o The path and query components of the request resource URI. + + o The protocol parameters excluding the "oauth_signature". + + o Parameters included in the request entity-body if they comply with + the strict restrictions defined in Section 3.4.1.3. + + The signature base string does not cover the entire HTTP request. + Most notably, it does not include the entity-body in most requests, + nor does it include most HTTP entity-headers. It is important to + note that the server cannot verify the authenticity of the excluded + request components without using additional protections such as SSL/ + TLS or other methods. + +3.4.1.1. String Construction + + The signature base string is constructed by concatenating together, + in order, the following HTTP request elements: + + 1. The HTTP request method in uppercase. For example: "HEAD", + "GET", "POST", etc. If the request uses a custom HTTP method, it + MUST be encoded (Section 3.6). + + 2. An "&" character (ASCII code 38). + + 3. The base string URI from Section 3.4.1.2, after being encoded + (Section 3.6). + + 4. An "&" character (ASCII code 38). + + 5. The request parameters as normalized in Section 3.4.1.3.2, after + being encoded (Section 3.6). + + For example, the HTTP request: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" + + c2&a3=2+q + + + + +Hammer-Lahav Informational [Page 19] + +RFC 5849 OAuth 1.0 April 2010 + + + is represented by the following signature base string (line breaks + are for display purposes only): + + POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q + %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_ + key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m + ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk + 9d7dh3k39sjv7 + +3.4.1.2. Base String URI + + The scheme, authority, and path of the request resource URI [RFC3986] + are included by constructing an "http" or "https" URI representing + the request resource (without the query or fragment) as follows: + + 1. The scheme and host MUST be in lowercase. + + 2. The host and port values MUST match the content of the HTTP + request "Host" header field. + + 3. The port MUST be included if it is not the default port for the + scheme, and MUST be excluded if it is the default. Specifically, + the port MUST be excluded when making an HTTP request [RFC2616] + to port 80 or when making an HTTPS request [RFC2818] to port 443. + All other non-default port numbers MUST be included. + + For example, the HTTP request: + + GET /r%20v/X?id=123 HTTP/1.1 + Host: EXAMPLE.COM:80 + + is represented by the base string URI: "http://example.com/r%20v/X". + + In another example, the HTTPS request: + + GET /?q=1 HTTP/1.1 + Host: www.example.net:8080 + + is represented by the base string URI: + "https://www.example.net:8080/". + +3.4.1.3. Request Parameters + + In order to guarantee a consistent and reproducible representation of + the request parameters, the parameters are collected and decoded to + their original decoded form. They are then sorted and encoded in a + particular manner that is often different from their original + encoding scheme, and concatenated into a single string. + + + +Hammer-Lahav Informational [Page 20] + +RFC 5849 OAuth 1.0 April 2010 + + +3.4.1.3.1. Parameter Sources + + The parameters from the following sources are collected into a single + list of name/value pairs: + + o The query component of the HTTP request URI as defined by + [RFC3986], Section 3.4. The query component is parsed into a list + of name/value pairs by treating it as an + "application/x-www-form-urlencoded" string, separating the names + and values and decoding them as defined by + [W3C.REC-html40-19980424], Section 17.13.4. + + o The OAuth HTTP "Authorization" header field (Section 3.5.1) if + present. The header's content is parsed into a list of name/value + pairs excluding the "realm" parameter if present. The parameter + values are decoded as defined by Section 3.5.1. + + o The HTTP request entity-body, but only if all of the following + conditions are met: + + * The entity-body is single-part. + + * The entity-body follows the encoding requirements of the + "application/x-www-form-urlencoded" content-type as defined by + [W3C.REC-html40-19980424]. + + * The HTTP request entity-header includes the "Content-Type" + header field set to "application/x-www-form-urlencoded". + + The entity-body is parsed into a list of decoded name/value pairs + as described in [W3C.REC-html40-19980424], Section 17.13.4. + + The "oauth_signature" parameter MUST be excluded from the signature + base string if present. Parameters not explicitly included in the + request MUST be excluded from the signature base string (e.g., the + "oauth_version" parameter when omitted). + + + + + + + + + + + + + + + +Hammer-Lahav Informational [Page 21] + +RFC 5849 OAuth 1.0 April 2010 + + + For example, the HTTP request: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D" + + c2&a3=2+q + + contains the following (fully decoded) parameters used in the + signature base sting: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | b5 | =%3D | + | a3 | a | + | c@ | | + | a2 | r b | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_token | kkk9d7dh3k39sjv7 | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_nonce | 7d8f3e4a | + | c2 | | + | a3 | 2 q | + +------------------------+------------------+ + + Note that the value of "b5" is "=%3D" and not "==". Both "c@" and + "c2" have empty values. While the encoding rules specified in this + specification for the purpose of constructing the signature base + string exclude the use of a "+" character (ASCII code 43) to + represent an encoded space character (ASCII code 32), this practice + is widely used in "application/x-www-form-urlencoded" encoded values, + and MUST be properly decoded, as demonstrated by one of the "a3" + parameter instances (the "a3" parameter is used twice in this + request). + + + + + + + + +Hammer-Lahav Informational [Page 22] + +RFC 5849 OAuth 1.0 April 2010 + + +3.4.1.3.2. Parameters Normalization + + The parameters collected in Section 3.4.1.3 are normalized into a + single string as follows: + + 1. First, the name and value of each parameter are encoded + (Section 3.6). + + 2. The parameters are sorted by name, using ascending byte value + ordering. If two or more parameters share the same name, they + are sorted by their value. + + 3. The name of each parameter is concatenated to its corresponding + value using an "=" character (ASCII code 61) as a separator, even + if the value is empty. + + 4. The sorted name/value pairs are concatenated together into a + single string by using an "&" character (ASCII code 38) as + separator. + + For example, the list of parameters from the previous section would + be normalized as follows: + + Encoded: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | b5 | %3D%253D | + | a3 | a | + | c%40 | | + | a2 | r%20b | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_token | kkk9d7dh3k39sjv7 | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_nonce | 7d8f3e4a | + | c2 | | + | a3 | 2%20q | + +------------------------+------------------+ + + + + + + + + + + + +Hammer-Lahav Informational [Page 23] + +RFC 5849 OAuth 1.0 April 2010 + + + Sorted: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | a2 | r%20b | + | a3 | 2%20q | + | a3 | a | + | b5 | %3D%253D | + | c%40 | | + | c2 | | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_nonce | 7d8f3e4a | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_token | kkk9d7dh3k39sjv7 | + +------------------------+------------------+ + + Concatenated Pairs: + + +-------------------------------------+ + | Name=Value | + +-------------------------------------+ + | a2=r%20b | + | a3=2%20q | + | a3=a | + | b5=%3D%253D | + | c%40= | + | c2= | + | oauth_consumer_key=9djdj82h48djs9d2 | + | oauth_nonce=7d8f3e4a | + | oauth_signature_method=HMAC-SHA1 | + | oauth_timestamp=137131201 | + | oauth_token=kkk9d7dh3k39sjv7 | + +-------------------------------------+ + + and concatenated together into a single string (line breaks are for + display purposes only): + + a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj + dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1 + &oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7 + + + + + + + + + +Hammer-Lahav Informational [Page 24] + +RFC 5849 OAuth 1.0 April 2010 + + +3.4.2. HMAC-SHA1 + + The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature + algorithm as defined in [RFC2104]: + + digest = HMAC-SHA1 (key, text) + + The HMAC-SHA1 function variables are used in following way: + + text is set to the value of the signature base string from + Section 3.4.1.1. + + key is set to the concatenated values of: + + 1. The client shared-secret, after being encoded + (Section 3.6). + + 2. An "&" character (ASCII code 38), which MUST be included + even when either secret is empty. + + 3. The token shared-secret, after being encoded + (Section 3.6). + + digest is used to set the value of the "oauth_signature" protocol + parameter, after the result octet string is base64-encoded + per [RFC2045], Section 6.8. + +3.4.3. RSA-SHA1 + + The "RSA-SHA1" signature method uses the RSASSA-PKCS1-v1_5 signature + algorithm as defined in [RFC3447], Section 8.2 (also known as + PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5. To + use this method, the client MUST have established client credentials + with the server that included its RSA public key (in a manner that is + beyond the scope of this specification). + + The signature base string is signed using the client's RSA private + key per [RFC3447], Section 8.2.1: + + S = RSASSA-PKCS1-V1_5-SIGN (K, M) + + Where: + + K is set to the client's RSA private key, + + M is set to the value of the signature base string from + Section 3.4.1.1, and + + + + +Hammer-Lahav Informational [Page 25] + +RFC 5849 OAuth 1.0 April 2010 + + + S is the result signature used to set the value of the + "oauth_signature" protocol parameter, after the result octet + string is base64-encoded per [RFC2045] section 6.8. + + The server verifies the signature per [RFC3447] section 8.2.2: + + RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S) + + Where: + + (n, e) is set to the client's RSA public key, + + M is set to the value of the signature base string from + Section 3.4.1.1, and + + S is set to the octet string value of the "oauth_signature" + protocol parameter received from the client. + +3.4.4. PLAINTEXT + + The "PLAINTEXT" method does not employ a signature algorithm. It + MUST be used with a transport-layer mechanism such as TLS or SSL (or + sent over a secure channel with equivalent protections). It does not + utilize the signature base string or the "oauth_timestamp" and + "oauth_nonce" parameters. + + The "oauth_signature" protocol parameter is set to the concatenated + value of: + + 1. The client shared-secret, after being encoded (Section 3.6). + + 2. An "&" character (ASCII code 38), which MUST be included even + when either secret is empty. + + 3. The token shared-secret, after being encoded (Section 3.6). + +3.5. Parameter Transmission + + When making an OAuth-authenticated request, protocol parameters as + well as any other parameter using the "oauth_" prefix SHALL be + included in the request using one and only one of the following + locations, listed in order of decreasing preference: + + 1. The HTTP "Authorization" header field as described in + Section 3.5.1. + + 2. The HTTP request entity-body as described in Section 3.5.2. + + + + +Hammer-Lahav Informational [Page 26] + +RFC 5849 OAuth 1.0 April 2010 + + + 3. The HTTP request URI query as described in Section 3.5.3. + + In addition to these three methods, future extensions MAY define + other methods for including protocol parameters in the request. + +3.5.1. Authorization Header + + Protocol parameters can be transmitted using the HTTP "Authorization" + header field as defined by [RFC2617] with the auth-scheme name set to + "OAuth" (case insensitive). + + For example: + + Authorization: OAuth realm="Example", + oauth_consumer_key="0685bd9184jfhq22", + oauth_token="ad180jjd733klru7", + oauth_signature_method="HMAC-SHA1", + oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + oauth_timestamp="137131200", + oauth_nonce="4572616e48616d6d65724c61686176", + oauth_version="1.0" + + Protocol parameters SHALL be included in the "Authorization" header + field as follows: + + 1. Parameter names and values are encoded per Parameter Encoding + (Section 3.6). + + 2. Each parameter's name is immediately followed by an "=" character + (ASCII code 61), a """ character (ASCII code 34), the parameter + value (MAY be empty), and another """ character (ASCII code 34). + + 3. Parameters are separated by a "," character (ASCII code 44) and + OPTIONAL linear whitespace per [RFC2617]. + + 4. The OPTIONAL "realm" parameter MAY be added and interpreted per + [RFC2617] section 1.2. + + Servers MAY indicate their support for the "OAuth" auth-scheme by + returning the HTTP "WWW-Authenticate" response header field upon + client requests for protected resources. As per [RFC2617], such a + response MAY include additional HTTP "WWW-Authenticate" header + fields: + + For example: + + WWW-Authenticate: OAuth realm="http://server.example.com/" + + + + +Hammer-Lahav Informational [Page 27] + +RFC 5849 OAuth 1.0 April 2010 + + + The realm parameter defines a protection realm per [RFC2617], Section + 1.2. + +3.5.2. Form-Encoded Body + + Protocol parameters can be transmitted in the HTTP request entity- + body, but only if the following REQUIRED conditions are met: + + o The entity-body is single-part. + + o The entity-body follows the encoding requirements of the + "application/x-www-form-urlencoded" content-type as defined by + [W3C.REC-html40-19980424]. + + o The HTTP request entity-header includes the "Content-Type" header + field set to "application/x-www-form-urlencoded". + + For example (line breaks are for display purposes only): + + oauth_consumer_key=0685bd9184jfhq22&oauth_token=ad180jjd733klr + u7&oauth_signature_method=HMAC-SHA1&oauth_signature=wOJIO9A2W5 + mFwDgiDvZbTSMK%2FPY%3D&oauth_timestamp=137131200&oauth_nonce=4 + 572616e48616d6d65724c61686176&oauth_version=1.0 + + The entity-body MAY include other request-specific parameters, in + which case, the protocol parameters SHOULD be appended following the + request-specific parameters, properly separated by an "&" character + (ASCII code 38). + +3.5.3. Request URI Query + + Protocol parameters can be transmitted by being added to the HTTP + request URI as a query parameter as defined by [RFC3986], Section 3. + + For example (line breaks are for display purposes only): + + GET /example/path?oauth_consumer_key=0685bd9184jfhq22& + oauth_token=ad180jjd733klru7&oauth_signature_method=HM + AC-SHA1&oauth_signature=wOJIO9A2W5mFwDgiDvZbTSMK%2FPY% + 3D&oauth_timestamp=137131200&oauth_nonce=4572616e48616 + d6d65724c61686176&oauth_version=1.0 HTTP/1.1 + + The request URI MAY include other request-specific query parameters, + in which case, the protocol parameters SHOULD be appended following + the request-specific parameters, properly separated by an "&" + character (ASCII code 38). + + + + + +Hammer-Lahav Informational [Page 28] + +RFC 5849 OAuth 1.0 April 2010 + + +3.6. Percent Encoding + + Existing percent-encoding methods do not guarantee a consistent + construction of the signature base string. The following percent- + encoding method is not defined to replace the existing encoding + methods defined by [RFC3986] and [W3C.REC-html40-19980424]. It is + used only in the construction of the signature base string and the + "Authorization" header field. + + This specification defines the following method for percent-encoding + strings: + + 1. Text values are first encoded as UTF-8 octets per [RFC3629] if + they are not already. This does not include binary values that + are not intended for human consumption. + + 2. The values are then escaped using the [RFC3986] percent-encoding + (%XX) mechanism as follows: + + * Characters in the unreserved character set as defined by + [RFC3986], Section 2.3 (ALPHA, DIGIT, "-", ".", "_", "~") MUST + NOT be encoded. + + * All other characters MUST be encoded. + + * The two hexadecimal characters used to represent encoded + characters MUST be uppercase. + + This method is different from the encoding scheme used by the + "application/x-www-form-urlencoded" content-type (for example, it + encodes space characters as "%20" and not using the "+" character). + It MAY be different from the percent-encoding functions provided by + web-development frameworks (e.g., encode different characters, use + lowercase hexadecimal characters). + +4. Security Considerations + + As stated in [RFC2617], the greatest sources of risks are usually + found not in the core protocol itself but in policies and procedures + surrounding its use. Implementers are strongly encouraged to assess + how this protocol addresses their security requirements. + +4.1. RSA-SHA1 Signature Method + + Authenticated requests made with "RSA-SHA1" signatures do not use the + token shared-secret, or any provisioned client shared-secret. This + means the request relies completely on the secrecy of the private key + used by the client to sign requests. + + + +Hammer-Lahav Informational [Page 29] + +RFC 5849 OAuth 1.0 April 2010 + + +4.2. Confidentiality of Requests + + While this protocol provides a mechanism for verifying the integrity + of requests, it provides no guarantee of request confidentiality. + Unless further precautions are taken, eavesdroppers will have full + access to request content. Servers should carefully consider the + kinds of data likely to be sent as part of such requests, and should + employ transport-layer security mechanisms to protect sensitive + resources. + +4.3. Spoofing by Counterfeit Servers + + This protocol makes no attempt to verify the authenticity of the + server. A hostile party could take advantage of this by intercepting + the client's requests and returning misleading or otherwise incorrect + responses. Service providers should consider such attacks when + developing services using this protocol, and should require + transport-layer security for any requests where the authenticity of + the server or of request responses is an issue. + +4.4. Proxying and Caching of Authenticated Content + + The HTTP Authorization scheme (Section 3.5.1) is optional. However, + [RFC2616] relies on the "Authorization" and "WWW-Authenticate" header + fields to distinguish authenticated content so that it can be + protected. Proxies and caches, in particular, may fail to adequately + protect requests not using these header fields. + + For example, private authenticated content may be stored in (and thus + retrievable from) publicly accessible caches. Servers not using the + HTTP "Authorization" header field should take care to use other + mechanisms, such as the "Cache-Control" header field, to ensure that + authenticated content is protected. + +4.5. Plaintext Storage of Credentials + + The client shared-secret and token shared-secret function the same + way passwords do in traditional authentication systems. In order to + compute the signatures used in methods other than "RSA-SHA1", the + server must have access to these secrets in plaintext form. This is + in contrast, for example, to modern operating systems, which store + only a one-way hash of user credentials. + + If an attacker were to gain access to these secrets -- or worse, to + the server's database of all such secrets -- he or she would be able + to perform any action on behalf of any resource owner. Accordingly, + it is critical that servers protect these secrets from unauthorized + access. + + + +Hammer-Lahav Informational [Page 30] + +RFC 5849 OAuth 1.0 April 2010 + + +4.6. Secrecy of the Client Credentials + + In many cases, the client application will be under the control of + potentially untrusted parties. For example, if the client is a + desktop application with freely available source code or an + executable binary, an attacker may be able to download a copy for + analysis. In such cases, attackers will be able to recover the + client credentials. + + Accordingly, servers should not use the client credentials alone to + verify the identity of the client. Where possible, other factors + such as IP address should be used as well. + +4.7. Phishing Attacks + + Wide deployment of this and similar protocols may cause resource + owners to become inured to the practice of being redirected to + websites where they are asked to enter their passwords. If resource + owners are not careful to verify the authenticity of these websites + before entering their credentials, it will be possible for attackers + to exploit this practice to steal resource owners' passwords. + + Servers should attempt to educate resource owners about the risks + phishing attacks pose, and should provide mechanisms that make it + easy for resource owners to confirm the authenticity of their sites. + Client developers should consider the security implications of how + they interact with a user-agent (e.g., separate window, embedded), + and the ability of the end-user to verify the authenticity of the + server website. + +4.8. Scoping of Access Requests + + By itself, this protocol does not provide any method for scoping the + access rights granted to a client. However, most applications do + require greater granularity of access rights. For example, servers + may wish to make it possible to grant access to some protected + resources but not others, or to grant only limited access (such as + read-only access) to those protected resources. + + When implementing this protocol, servers should consider the types of + access resource owners may wish to grant clients, and should provide + mechanisms to do so. Servers should also take care to ensure that + resource owners understand the access they are granting, as well as + any risks that may be involved. + + + + + + + +Hammer-Lahav Informational [Page 31] + +RFC 5849 OAuth 1.0 April 2010 + + +4.9. Entropy of Secrets + + Unless a transport-layer security protocol is used, eavesdroppers + will have full access to authenticated requests and signatures, and + will thus be able to mount offline brute-force attacks to recover the + credentials used. Servers should be careful to assign shared-secrets + that are long enough, and random enough, to resist such attacks for + at least the length of time that the shared-secrets are valid. + + For example, if shared-secrets are valid for two weeks, servers + should ensure that it is not possible to mount a brute force attack + that recovers the shared-secret in less than two weeks. Of course, + servers are urged to err on the side of caution, and use the longest + secrets reasonable. + + It is equally important that the pseudo-random number generator + (PRNG) used to generate these secrets be of sufficiently high + quality. Many PRNG implementations generate number sequences that + may appear to be random, but that nevertheless exhibit patterns or + other weaknesses that make cryptanalysis or brute force attacks + easier. Implementers should be careful to use cryptographically + secure PRNGs to avoid these problems. + +4.10. Denial-of-Service / Resource-Exhaustion Attacks + + This specification includes a number of features that may make + resource exhaustion attacks against servers possible. For example, + this protocol requires servers to track used nonces. If an attacker + is able to use many nonces quickly, the resources required to track + them may exhaust available capacity. And again, this protocol can + require servers to perform potentially expensive computations in + order to verify the signature on incoming requests. An attacker may + exploit this to perform a denial-of-service attack by sending a large + number of invalid requests to the server. + + Resource Exhaustion attacks are by no means specific to this + specification. However, implementers should be careful to consider + the additional avenues of attack that this protocol exposes, and + design their implementations accordingly. For example, entropy + starvation typically results in either a complete denial of service + while the system waits for new entropy or else in weak (easily + guessable) secrets. When implementing this protocol, servers should + consider which of these presents a more serious risk for their + application and design accordingly. + + + + + + + +Hammer-Lahav Informational [Page 32] + +RFC 5849 OAuth 1.0 April 2010 + + +4.11. SHA-1 Cryptographic Attacks + + SHA-1, the hash algorithm used in "HMAC-SHA1" and "RSA-SHA1" + signature methods, has been shown to have a number of cryptographic + weaknesses that significantly reduce its resistance to collision + attacks. While these weaknesses do not seem to affect the use of + SHA-1 with the Hash-based Message Authentication Code (HMAC) and + should not affect the "HMAC-SHA1" signature method, it may affect the + use of the "RSA-SHA1" signature method. NIST has announced that it + will phase out use of SHA-1 in digital signatures by 2010 + [NIST_SHA-1Comments]. + + Practically speaking, these weaknesses are difficult to exploit, and + by themselves do not pose a significant risk to users of this + protocol. They may, however, make more efficient attacks possible, + and servers should take this into account when considering whether + SHA-1 provides an adequate level of security for their applications. + +4.12. Signature Base String Limitations + + The signature base string has been designed to support the signature + methods defined in this specification. Those designing additional + signature methods, should evaluated the compatibility of the + signature base string with their security requirements. + + Since the signature base string does not cover the entire HTTP + request, such as most request entity-body, most entity-headers, and + the order in which parameters are sent, servers should employ + additional mechanisms to protect such elements. + +4.13. Cross-Site Request Forgery (CSRF) + + Cross-Site Request Forgery (CSRF) is a web-based attack whereby HTTP + requests are transmitted from a user that the website trusts or has + authenticated. CSRF attacks on authorization approvals can allow an + attacker to obtain authorization to protected resources without the + consent of the User. Servers SHOULD strongly consider best practices + in CSRF prevention at all the protocol authorization endpoints. + + CSRF attacks on OAuth callback URIs hosted by clients are also + possible. Clients should prevent CSRF attacks on OAuth callback URIs + by verifying that the resource owner at the client site intended to + complete the OAuth negotiation with the server. The methods for + preventing such CSRF attacks are beyond the scope of this + specification. + + + + + + +Hammer-Lahav Informational [Page 33] + +RFC 5849 OAuth 1.0 April 2010 + + +4.14. User Interface Redress + + Servers should protect the authorization process against user + interface (UI) redress attacks (also known as "clickjacking"). As of + the time of this writing, no complete defenses against UI redress are + available. Servers can mitigate the risk of UI redress attacks using + the following techniques: + + o JavaScript frame busting. + + o JavaScript frame busting, and requiring that browsers have + JavaScript enabled on the authorization page. + + o Browser-specific anti-framing techniques. + + o Requiring password reentry before issuing OAuth tokens. + +4.15. Automatic Processing of Repeat Authorizations + + Servers may wish to automatically process authorization requests + (Section 2.2) from clients that have been previously authorized by + the resource owner. When the resource owner is redirected to the + server to grant access, the server detects that the resource owner + has already granted access to that particular client. Instead of + prompting the resource owner for approval, the server automatically + redirects the resource owner back to the client. + + If the client credentials are compromised, automatic processing + creates additional security risks. An attacker can use the stolen + client credentials to redirect the resource owner to the server with + an authorization request. The server will then grant access to the + resource owner's data without the resource owner's explicit approval, + or even awareness of an attack. If no automatic approval is + implemented, an attacker must use social engineering to convince the + resource owner to approve access. + + Servers can mitigate the risks associated with automatic processing + by limiting the scope of token credentials obtained through automated + approvals. Tokens credentials obtained through explicit resource + owner consent can remain unaffected. Clients can mitigate the risks + associated with automatic processing by protecting their client + credentials. + + + + + + + + + +Hammer-Lahav Informational [Page 34] + +RFC 5849 OAuth 1.0 April 2010 + + +5. Acknowledgments + + This specification is directly based on the OAuth Core 1.0 Revision A + community specification, which in turn was modeled after existing + proprietary protocols and best practices that have been independently + implemented by various companies. + + The community specification was edited by Eran Hammer-Lahav and + authored by: Mark Atwood, Dirk Balfanz, Darren Bounds, Richard M. + Conlan, Blaine Cook, Leah Culver, Breno de Medeiros, Brian Eaton, + Kellan Elliott-McCrea, Larry Halff, Eran Hammer-Lahav, Ben Laurie, + Chris Messina, John Panzer, Sam Quigley, David Recordon, Eran + Sandler, Jonathan Sergent, Todd Sieling, Brian Slesinsky, and Andy + Smith. + + The editor would like to thank the following individuals for their + invaluable contribution to the publication of this edition of the + protocol: Lisa Dusseault, Justin Hart, Avshalom Houri, Chris Messina, + Mark Nottingham, Tim Polk, Peter Saint-Andre, Joseph Smarr, and Paul + Walker. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Hammer-Lahav Informational [Page 35] + +RFC 5849 OAuth 1.0 April 2010 + + +Appendix A. Differences from the Community Edition + + This specification includes the following changes made to the + original community document [OAuthCore1.0_RevisionA] in order to + correct mistakes and omissions identified since the document was + originally published at . + + o Changed using TLS/SSL when sending or requesting plain text + credentials from SHOULD to MUST. This change affects any use of + the "PLAINTEXT" signature method, as well as requesting temporary + credentials (Section 2.1) and obtaining token credentials + (Section 2.3). + + o Adjusted nonce language to indicate it is unique per token/ + timestamp/client combination. + + o Removed the requirement for timestamps to be equal to or greater + than the timestamp used in the previous request. + + o Changed the nonce and timestamp parameters to OPTIONAL when using + the "PLAINTEXT" signature method. + + o Extended signature base string coverage that includes + "application/x-www-form-urlencoded" entity-body parameters when + the HTTP method used is other than "POST" and URI query parameters + when the HTTP method used is other than "GET". + + o Incorporated corrections to the instructions in each signature + method to encode the signature value before inserting it into the + "oauth_signature" parameter, removing errors that would have + caused double-encoded values. + + o Allowed omitting the "oauth_token" parameter when empty. + + o Permitted sending requests for temporary credentials with an empty + "oauth_token" parameter. + + o Removed the restrictions from defining additional "oauth_" + parameters. + + + + + + + + + + + + +Hammer-Lahav Informational [Page 36] + +RFC 5849 OAuth 1.0 April 2010 + + +6. References + +6.1. Normative References + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, November 1996. + + [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed- + Hashing for Message Authentication", RFC 2104, + February 1997. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., + Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext + Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999. + + [RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., + Leach, P., Luotonen, A., and L. Stewart, "HTTP + Authentication: Basic and Digest Access Authentication", + RFC 2617, June 1999. + + [RFC2818] Rescorla, E., "HTTP Over TLS", RFC 2818, May 2000. + + [RFC3447] Jonsson, J. and B. Kaliski, "Public-Key Cryptography + Standards (PKCS) #1: RSA Cryptography Specifications + Version 2.1", RFC 3447, February 2003. + + [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform + Resource Identifier (URI): Generic Syntax", STD 66, + RFC 3986, January 2005. + + [W3C.REC-html40-19980424] + Hors, A., Raggett, D., and I. Jacobs, "HTML 4.0 + Specification", World Wide Web Consortium + Recommendation REC-html40-19980424, April 1998, + . + + + + + + + + + +Hammer-Lahav Informational [Page 37] + +RFC 5849 OAuth 1.0 April 2010 + + +6.2. Informative References + + [NIST_SHA-1Comments] + Burr, W., "NIST Comments on Cryptanalytic Attacks on + SHA-1", + . + + [OAuthCore1.0_RevisionA] + OAuth Community, "OAuth Core 1.0 Revision A", + . + +Author's Address + + Eran Hammer-Lahav (editor) + + EMail: eran@hueniverse.com + URI: http://hueniverse.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Hammer-Lahav Informational [Page 38] + diff --git a/vendor/league/oauth1-client/src/Client/Credentials/ClientCredentials.php b/vendor/league/oauth1-client/src/Client/Credentials/ClientCredentials.php new file mode 100644 index 0000000000..3a977d4cc9 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Credentials/ClientCredentials.php @@ -0,0 +1,29 @@ +callbackUri; + } + + /** + * {@inheritDoc} + */ + public function setCallbackUri($callbackUri) + { + $this->callbackUri = $callbackUri; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Credentials/ClientCredentialsInterface.php b/vendor/league/oauth1-client/src/Client/Credentials/ClientCredentialsInterface.php new file mode 100644 index 0000000000..f50f97b3ec --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Credentials/ClientCredentialsInterface.php @@ -0,0 +1,20 @@ +identifier; + } + + /** + * {@inheritDoc} + */ + public function setIdentifier($identifier) + { + $this->identifier = $identifier; + } + + /** + * {@inheritDoc} + */ + public function getSecret() + { + return $this->secret; + } + + /** + * {@inheritDoc} + */ + public function setSecret($secret) + { + $this->secret = $secret; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Credentials/CredentialsException.php b/vendor/league/oauth1-client/src/Client/Credentials/CredentialsException.php new file mode 100644 index 0000000000..b512eaa441 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Credentials/CredentialsException.php @@ -0,0 +1,9 @@ +uid = $data['user']['username']; + $user->nickname = $data['user']['username']; + $user->name = $data['user']['display_name']; + $user->firstName = $data['user']['first_name']; + $user->lastName = $data['user']['last_name']; + $user->imageUrl = $data['user']['avatar']; + + $used = array('username', 'display_name', 'avatar'); + + foreach ($data as $key => $value) { + if (strpos($key, 'url') !== false) { + if (!in_array($key, $used)) { + $used[] = $key; + } + + $user->urls[$key] = $value; + } + } + + // Save all extra data + $user->extra = array_diff_key($data, array_flip($used)); + + return $user; + } + + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return $data['user']['username']; + } + + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return; + } + + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return $data['user']['display_name']; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Magento.php b/vendor/league/oauth1-client/src/Client/Server/Magento.php new file mode 100644 index 0000000000..bd9e006dd9 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Magento.php @@ -0,0 +1,212 @@ +parseConfigurationArray($clientCredentials); + } + } + + /** + * {@inheritDoc} + */ + public function urlTemporaryCredentials() + { + return $this->baseUri.'/oauth/initiate'; + } + + /** + * {@inheritDoc} + */ + public function urlAuthorization() + { + return $this->isAdmin + ? $this->adminUrl + : $this->baseUri.'/oauth/authorize'; + } + + /** + * {@inheritDoc} + */ + public function urlTokenCredentials() + { + return $this->baseUri.'/oauth/token'; + } + + /** + * {@inheritDoc} + */ + public function urlUserDetails() + { + return $this->baseUri.'/api/rest/customers'; + } + + /** + * {@inheritDoc} + */ + public function userDetails($data, TokenCredentials $tokenCredentials) + { + if (!is_array($data) || !count($data)) { + throw new \Exception('Not possible to get user info'); + } + + $id = key($data); + $data = current($data); + + $user = new User(); + $user->uid = $id; + + $mapping = array( + 'email' => 'email', + 'firstName' => 'firstname', + 'lastName' => 'lastname', + ); + foreach ($mapping as $userKey => $dataKey) { + if (!isset($data[$dataKey])) { + continue; + } + $user->{$userKey} = $data[$dataKey]; + } + + $user->extra = array_diff_key($data, array_flip($mapping)); + + return $user; + } + + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return key($data); + } + + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + $data = current($data); + if (!isset($data['email'])) { + return; + } + return $data['email']; + } + + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return; + } + + /** + * {@inheritDoc} + */ + public function getTokenCredentials(TemporaryCredentials $temporaryCredentials, $temporaryIdentifier, $verifier) + { + $this->verifier = $verifier; + + return parent::getTokenCredentials($temporaryCredentials, $temporaryIdentifier, $verifier); + } + + /** + * {@inheritDoc} + */ + protected function additionalProtocolParameters() + { + return array( + 'oauth_verifier' => $this->verifier, + ); + } + + protected function getHttpClientDefaultHeaders() + { + $defaultHeaders = parent::getHttpClientDefaultHeaders(); + // Accept header is required, @see Mage_Api2_Model_Renderer::factory + $defaultHeaders['Accept'] = 'application/json'; + + return $defaultHeaders; + } + + /** + * Parse configuration array to set attributes. + * + * @param array $configuration + * @throws \Exception + */ + private function parseConfigurationArray(array $configuration = array()) + { + if (!isset($configuration['host'])) { + throw new \Exception('Missing Magento Host'); + } + $url = parse_url($configuration['host']); + $this->baseUri = sprintf('%s://%s', $url['scheme'], $url['host']); + + if (isset($url['port'])) { + $this->baseUri .= ':'.$url['port']; + } + + if (isset($url['path'])) { + $this->baseUri .= '/'.trim($url['path'], '/'); + } + $this->isAdmin = !empty($configuration['admin']); + if (!empty($configuration['adminUrl'])) { + $this->adminUrl = $configuration['adminUrl'].'/oauth_authorize'; + } else { + $this->adminUrl = $this->baseUri.'/admin/oauth_authorize'; + } + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Server.php b/vendor/league/oauth1-client/src/Client/Server/Server.php new file mode 100644 index 0000000000..417688cba0 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Server.php @@ -0,0 +1,695 @@ +createClientCredentials($clientCredentials); + } elseif (!$clientCredentials instanceof ClientCredentialsInterface) { + throw new \InvalidArgumentException('Client credentials must be an array or valid object.'); + } + + $this->clientCredentials = $clientCredentials; + $this->signature = $signature ?: new HmacSha1Signature($clientCredentials); + } + + /** + * Gets temporary credentials by performing a request to + * the server. + * + * @return TemporaryCredentials + */ + public function getTemporaryCredentials() + { + $uri = $this->urlTemporaryCredentials(); + + $client = $this->createHttpClient(); + + $header = $this->temporaryCredentialsProtocolHeader($uri); + $authorizationHeader = array('Authorization' => $header); + $headers = $this->buildHttpClientHeaders($authorizationHeader); + + try { + $response = $client->post($uri, [ + 'headers' => $headers, + ]); + } catch (BadResponseException $e) { + return $this->handleTemporaryCredentialsBadResponse($e); + } + + return $this->createTemporaryCredentials((string) $response->getBody()); + } + + /** + * Get the authorization URL by passing in the temporary credentials + * identifier or an object instance. + * + * @param TemporaryCredentials|string $temporaryIdentifier + * + * @return string + */ + public function getAuthorizationUrl($temporaryIdentifier) + { + // Somebody can pass through an instance of temporary + // credentials and we'll extract the identifier from there. + if ($temporaryIdentifier instanceof TemporaryCredentials) { + $temporaryIdentifier = $temporaryIdentifier->getIdentifier(); + } + + $parameters = array('oauth_token' => $temporaryIdentifier); + + $url = $this->urlAuthorization(); + $queryString = http_build_query($parameters); + + return $this->buildUrl($url, $queryString); + } + + /** + * Redirect the client to the authorization URL. + * + * @param TemporaryCredentials|string $temporaryIdentifier + */ + public function authorize($temporaryIdentifier) + { + $url = $this->getAuthorizationUrl($temporaryIdentifier); + + header('Location: '.$url); + + return; + } + + /** + * Retrieves token credentials by passing in the temporary credentials, + * the temporary credentials identifier as passed back by the server + * and finally the verifier code. + * + * @param TemporaryCredentials $temporaryCredentials + * @param string $temporaryIdentifier + * @param string $verifier + * + * @return TokenCredentials + */ + public function getTokenCredentials(TemporaryCredentials $temporaryCredentials, $temporaryIdentifier, $verifier) + { + if ($temporaryIdentifier !== $temporaryCredentials->getIdentifier()) { + throw new \InvalidArgumentException( + 'Temporary identifier passed back by server does not match that of stored temporary credentials. + Potential man-in-the-middle.' + ); + } + + $uri = $this->urlTokenCredentials(); + $bodyParameters = array('oauth_verifier' => $verifier); + + $client = $this->createHttpClient(); + + $headers = $this->getHeaders($temporaryCredentials, 'POST', $uri, $bodyParameters); + + try { + $response = $client->post($uri, [ + 'headers' => $headers, + 'form_params' => $bodyParameters, + ]); + } catch (BadResponseException $e) { + return $this->handleTokenCredentialsBadResponse($e); + } + + return $this->createTokenCredentials((string) $response->getBody()); + } + + /** + * Get user details by providing valid token credentials. + * + * @param TokenCredentials $tokenCredentials + * @param bool $force + * + * @return \League\OAuth1\Client\Server\User + */ + public function getUserDetails(TokenCredentials $tokenCredentials, $force = false) + { + $data = $this->fetchUserDetails($tokenCredentials, $force); + + return $this->userDetails($data, $tokenCredentials); + } + + /** + * Get the user's unique identifier (primary key). + * + * @param TokenCredentials $tokenCredentials + * @param bool $force + * + * @return string|int + */ + public function getUserUid(TokenCredentials $tokenCredentials, $force = false) + { + $data = $this->fetchUserDetails($tokenCredentials, $force); + + return $this->userUid($data, $tokenCredentials); + } + + /** + * Get the user's email, if available. + * + * @param TokenCredentials $tokenCredentials + * @param bool $force + * + * @return string|null + */ + public function getUserEmail(TokenCredentials $tokenCredentials, $force = false) + { + $data = $this->fetchUserDetails($tokenCredentials, $force); + + return $this->userEmail($data, $tokenCredentials); + } + + /** + * Get the user's screen name (username), if available. + * + * @param TokenCredentials $tokenCredentials + * @param bool $force + * + * @return string + */ + public function getUserScreenName(TokenCredentials $tokenCredentials, $force = false) + { + $data = $this->fetchUserDetails($tokenCredentials, $force); + + return $this->userScreenName($data, $tokenCredentials); + } + + /** + * Fetch user details from the remote service. + * + * @param TokenCredentials $tokenCredentials + * @param bool $force + * + * @return array HTTP client response + */ + protected function fetchUserDetails(TokenCredentials $tokenCredentials, $force = true) + { + if (!$this->cachedUserDetailsResponse || $force) { + $url = $this->urlUserDetails(); + + $client = $this->createHttpClient(); + + $headers = $this->getHeaders($tokenCredentials, 'GET', $url); + + try { + $response = $client->get($url, [ + 'headers' => $headers, + ]); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + $body = $response->getBody(); + $statusCode = $response->getStatusCode(); + + throw new \Exception( + "Received error [$body] with status code [$statusCode] when retrieving token credentials." + ); + } + switch ($this->responseType) { + case 'json': + $this->cachedUserDetailsResponse = json_decode((string) $response->getBody(), true); + break; + + case 'xml': + $this->cachedUserDetailsResponse = simplexml_load_string((string) $response->getBody()); + break; + + case 'string': + parse_str((string) $response->getBody(), $this->cachedUserDetailsResponse); + break; + + default: + throw new \InvalidArgumentException("Invalid response type [{$this->responseType}]."); + } + } + + return $this->cachedUserDetailsResponse; + } + + /** + * Get the client credentials associated with the server. + * + * @return ClientCredentialsInterface + */ + public function getClientCredentials() + { + return $this->clientCredentials; + } + + /** + * Get the signature associated with the server. + * + * @return SignatureInterface + */ + public function getSignature() + { + return $this->signature; + } + + /** + * Creates a Guzzle HTTP client for the given URL. + * + * @return GuzzleHttpClient + */ + public function createHttpClient() + { + return new GuzzleHttpClient(); + } + + /** + * Set the user agent value. + * + * @param string $userAgent + * + * @return Server + */ + public function setUserAgent($userAgent = null) + { + $this->userAgent = $userAgent; + + return $this; + } + + /** + * Get all headers required to created an authenticated request. + * + * @param CredentialsInterface $credentials + * @param string $method + * @param string $url + * @param array $bodyParameters + * + * @return array + */ + public function getHeaders(CredentialsInterface $credentials, $method, $url, array $bodyParameters = array()) + { + $header = $this->protocolHeader(strtoupper($method), $url, $credentials, $bodyParameters); + $authorizationHeader = array('Authorization' => $header); + $headers = $this->buildHttpClientHeaders($authorizationHeader); + + return $headers; + } + + /** + * Get Guzzle HTTP client default headers. + * + * @return array + */ + protected function getHttpClientDefaultHeaders() + { + $defaultHeaders = array(); + if (!empty($this->userAgent)) { + $defaultHeaders['User-Agent'] = $this->userAgent; + } + + return $defaultHeaders; + } + + /** + * Build Guzzle HTTP client headers. + * + * @return array + */ + protected function buildHttpClientHeaders($headers = array()) + { + $defaultHeaders = $this->getHttpClientDefaultHeaders(); + + return array_merge($headers, $defaultHeaders); + } + + /** + * Creates a client credentials instance from an array of credentials. + * + * @param array $clientCredentials + * + * @return ClientCredentials + */ + protected function createClientCredentials(array $clientCredentials) + { + $keys = array('identifier', 'secret'); + + foreach ($keys as $key) { + if (!isset($clientCredentials[$key])) { + throw new \InvalidArgumentException("Missing client credentials key [$key] from options."); + } + } + + $_clientCredentials = new ClientCredentials(); + $_clientCredentials->setIdentifier($clientCredentials['identifier']); + $_clientCredentials->setSecret($clientCredentials['secret']); + + if (isset($clientCredentials['callback_uri'])) { + $_clientCredentials->setCallbackUri($clientCredentials['callback_uri']); + } + + return $_clientCredentials; + } + + /** + * Handle a bad response coming back when getting temporary credentials. + * + * @param BadResponseException $e + * + * @throws CredentialsException + */ + protected function handleTemporaryCredentialsBadResponse(BadResponseException $e) + { + $response = $e->getResponse(); + $body = $response->getBody(); + $statusCode = $response->getStatusCode(); + + throw new CredentialsException( + "Received HTTP status code [$statusCode] with message \"$body\" when getting temporary credentials." + ); + } + + /** + * Creates temporary credentials from the body response. + * + * @param string $body + * + * @return TemporaryCredentials + */ + protected function createTemporaryCredentials($body) + { + parse_str($body, $data); + + if (!$data || !is_array($data)) { + throw new CredentialsException('Unable to parse temporary credentials response.'); + } + + if (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] != 'true') { + throw new CredentialsException('Error in retrieving temporary credentials.'); + } + + $temporaryCredentials = new TemporaryCredentials(); + $temporaryCredentials->setIdentifier($data['oauth_token']); + $temporaryCredentials->setSecret($data['oauth_token_secret']); + + return $temporaryCredentials; + } + + /** + * Handle a bad response coming back when getting token credentials. + * + * @param BadResponseException $e + * + * @throws CredentialsException + */ + protected function handleTokenCredentialsBadResponse(BadResponseException $e) + { + $response = $e->getResponse(); + $body = $response->getBody(); + $statusCode = $response->getStatusCode(); + + throw new CredentialsException( + "Received HTTP status code [$statusCode] with message \"$body\" when getting token credentials." + ); + } + + /** + * Creates token credentials from the body response. + * + * @param string $body + * + * @return TokenCredentials + */ + protected function createTokenCredentials($body) + { + parse_str($body, $data); + + if (!$data || !is_array($data)) { + throw new CredentialsException('Unable to parse token credentials response.'); + } + + if (isset($data['error'])) { + throw new CredentialsException("Error [{$data['error']}] in retrieving token credentials."); + } + + $tokenCredentials = new TokenCredentials(); + $tokenCredentials->setIdentifier($data['oauth_token']); + $tokenCredentials->setSecret($data['oauth_token_secret']); + + return $tokenCredentials; + } + + /** + * Get the base protocol parameters for an OAuth request. + * Each request builds on these parameters. + * + * @return array + * + * @see OAuth 1.0 RFC 5849 Section 3.1 + */ + protected function baseProtocolParameters() + { + $dateTime = new \DateTime(); + + return array( + 'oauth_consumer_key' => $this->clientCredentials->getIdentifier(), + 'oauth_nonce' => $this->nonce(), + 'oauth_signature_method' => $this->signature->method(), + 'oauth_timestamp' => $dateTime->format('U'), + 'oauth_version' => '1.0', + ); + } + + /** + * Any additional required protocol parameters for an + * OAuth request. + * + * @return array + */ + protected function additionalProtocolParameters() + { + return array(); + } + + /** + * Generate the OAuth protocol header for a temporary credentials + * request, based on the URI. + * + * @param string $uri + * + * @return string + */ + protected function temporaryCredentialsProtocolHeader($uri) + { + $parameters = array_merge($this->baseProtocolParameters(), array( + 'oauth_callback' => $this->clientCredentials->getCallbackUri(), + )); + + $parameters['oauth_signature'] = $this->signature->sign($uri, $parameters, 'POST'); + + return $this->normalizeProtocolParameters($parameters); + } + + /** + * Generate the OAuth protocol header for requests other than temporary + * credentials, based on the URI, method, given credentials & body query + * string. + * + * @param string $method + * @param string $uri + * @param CredentialsInterface $credentials + * @param array $bodyParameters + * + * @return string + */ + protected function protocolHeader($method, $uri, CredentialsInterface $credentials, array $bodyParameters = array()) + { + $parameters = array_merge( + $this->baseProtocolParameters(), + $this->additionalProtocolParameters(), + array( + 'oauth_token' => $credentials->getIdentifier(), + ) + ); + + $this->signature->setCredentials($credentials); + + $parameters['oauth_signature'] = $this->signature->sign( + $uri, + array_merge($parameters, $bodyParameters), + $method + ); + + return $this->normalizeProtocolParameters($parameters); + } + + /** + * Takes an array of protocol parameters and normalizes them + * to be used as a HTTP header. + * + * @param array $parameters + * + * @return string + */ + protected function normalizeProtocolParameters(array $parameters) + { + array_walk($parameters, function (&$value, $key) { + $value = rawurlencode($key).'="'.rawurlencode($value).'"'; + }); + + return 'OAuth '.implode(', ', $parameters); + } + + /** + * Generate a random string. + * + * @param int $length + * + * @return string + * + * @see OAuth 1.0 RFC 5849 Section 3.3 + */ + protected function nonce($length = 32) + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return substr(str_shuffle(str_repeat($pool, 5)), 0, $length); + } + + /** + * Build a url by combining hostname and query string after checking for + * exisiting '?' character in host. + * + * @param string $host + * @param string $queryString + * + * @return string + */ + protected function buildUrl($host, $queryString) + { + return $host.(strpos($host, '?') !== false ? '&' : '?').$queryString; + } + + /** + * Get the URL for retrieving temporary credentials. + * + * @return string + */ + abstract public function urlTemporaryCredentials(); + + /** + * Get the URL for redirecting the resource owner to authorize the client. + * + * @return string + */ + abstract public function urlAuthorization(); + + /** + * Get the URL retrieving token credentials. + * + * @return string + */ + abstract public function urlTokenCredentials(); + + /** + * Get the URL for retrieving user details. + * + * @return string + */ + abstract public function urlUserDetails(); + + /** + * Take the decoded data from the user details URL and convert + * it to a User object. + * + * @param mixed $data + * @param TokenCredentials $tokenCredentials + * + * @return User + */ + abstract public function userDetails($data, TokenCredentials $tokenCredentials); + + /** + * Take the decoded data from the user details URL and extract + * the user's UID. + * + * @param mixed $data + * @param TokenCredentials $tokenCredentials + * + * @return string|int + */ + abstract public function userUid($data, TokenCredentials $tokenCredentials); + + /** + * Take the decoded data from the user details URL and extract + * the user's email. + * + * @param mixed $data + * @param TokenCredentials $tokenCredentials + * + * @return string + */ + abstract public function userEmail($data, TokenCredentials $tokenCredentials); + + /** + * Take the decoded data from the user details URL and extract + * the user's screen name. + * + * @param mixed $data + * @param TokenCredentials $tokenCredentials + * + * @return string + */ + abstract public function userScreenName($data, TokenCredentials $tokenCredentials); +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Trello.php b/vendor/league/oauth1-client/src/Client/Server/Trello.php new file mode 100644 index 0000000000..7f9e944d6d --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Trello.php @@ -0,0 +1,252 @@ +parseConfiguration($clientCredentials); + } + } + + /** + * Set the access token. + * + * @param string $accessToken + * + * @return Trello + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + + return $this; + } + + /** + * Set the application expiration. + * + * @param string $applicationExpiration + * + * @return Trello + */ + public function setApplicationExpiration($applicationExpiration) + { + $this->applicationExpiration = $applicationExpiration; + + return $this; + } + + /** + * Get application expiration. + * + * @return string + */ + public function getApplicationExpiration() + { + return $this->applicationExpiration ?: '1day'; + } + + /** + * Set the application name. + * + * @param string $applicationName + * + * @return Trello + */ + public function setApplicationName($applicationName) + { + $this->applicationName = $applicationName; + + return $this; + } + + /** + * Get application name. + * + * @return string|null + */ + public function getApplicationName() + { + return $this->applicationName ?: null; + } + + /** + * Set the application scope. + * + * @param string $applicationScope + * + * @return Trello + */ + public function setApplicationScope($applicationScope) + { + $this->applicationScope = $applicationScope; + + return $this; + } + + /** + * Get application scope. + * + * @return string + */ + public function getApplicationScope() + { + return $this->applicationScope ?: 'read'; + } + + /** + * {@inheritDoc} + */ + public function urlTemporaryCredentials() + { + return 'https://trello.com/1/OAuthGetRequestToken'; + } + + /** + * {@inheritDoc} + */ + public function urlAuthorization() + { + return 'https://trello.com/1/OAuthAuthorizeToken?'. + $this->buildAuthorizationQueryParameters(); + } + + /** + * {@inheritDoc} + */ + public function urlTokenCredentials() + { + return 'https://trello.com/1/OAuthGetAccessToken'; + } + + /** + * {@inheritDoc} + */ + public function urlUserDetails() + { + return 'https://trello.com/1/members/me?key='.$this->applicationKey.'&token='.$this->accessToken; + } + + /** + * {@inheritDoc} + */ + public function userDetails($data, TokenCredentials $tokenCredentials) + { + $user = new User(); + + $user->nickname = $data['username']; + $user->name = $data['fullName']; + $user->imageUrl = null; + + $user->extra = (array) $data; + + return $user; + } + + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return $data['id']; + } + + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return; + } + + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return $data['username']; + } + + /** + * Build authorization query parameters. + * + * @return string + */ + private function buildAuthorizationQueryParameters() + { + $params = array( + 'response_type' => 'fragment', + 'scope' => $this->getApplicationScope(), + 'expiration' => $this->getApplicationExpiration(), + 'name' => $this->getApplicationName(), + ); + + return http_build_query($params); + } + + /** + * Parse configuration array to set attributes. + * + * @param array $configuration + */ + private function parseConfiguration(array $configuration = array()) + { + $configToPropertyMap = array( + 'identifier' => 'applicationKey', + 'expiration' => 'applicationExpiration', + 'name' => 'applicationName', + 'scope' => 'applicationScope', + ); + + foreach ($configToPropertyMap as $config => $property) { + if (isset($configuration[$config])) { + $this->$property = $configuration[$config]; + } + } + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Tumblr.php b/vendor/league/oauth1-client/src/Client/Server/Tumblr.php new file mode 100644 index 0000000000..51ffb040ca --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Tumblr.php @@ -0,0 +1,99 @@ +nickname = $data['name']; + + // Save all extra data + $used = array('name'); + $user->extra = array_diff_key($data, array_flip($used)); + + return $user; + } + + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + if (!isset($data['response']['user']) || !is_array($data['response']['user'])) { + return; + } + + $data = $data['response']['user']; + + return $data['name']; + } + + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return; + } + + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + if (!isset($data['response']['user']) || !is_array($data['response']['user'])) { + return; + } + + $data = $data['response']['user']; + + return $data['name']; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Twitter.php b/vendor/league/oauth1-client/src/Client/Server/Twitter.php new file mode 100644 index 0000000000..cca204329f --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Twitter.php @@ -0,0 +1,100 @@ +uid = $data['id_str']; + $user->nickname = $data['screen_name']; + $user->name = $data['name']; + $user->location = $data['location']; + $user->description = $data['description']; + $user->imageUrl = $data['profile_image_url']; + $user->email = null; + if (isset($data['email'])) { + $user->email = $data['email']; + } + + $used = array('id', 'screen_name', 'name', 'location', 'description', 'profile_image_url', 'email'); + + foreach ($data as $key => $value) { + if (strpos($key, 'url') !== false) { + if (!in_array($key, $used)) { + $used[] = $key; + } + + $user->urls[$key] = $value; + } + } + + // Save all extra data + $user->extra = array_diff_key($data, array_flip($used)); + + return $user; + } + + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return $data['id']; + } + + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return; + } + + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return $data['name']; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/User.php b/vendor/league/oauth1-client/src/Client/Server/User.php new file mode 100644 index 0000000000..95c2567e29 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/User.php @@ -0,0 +1,118 @@ +{$key})) { + $this->{$key} = $value; + } + } + + /** + * Get a property from the user. + * + * @param string $key + * + * @return mixed + */ + public function __get($key) + { + if (isset($this->{$key})) { + return $this->{$key}; + } + } + + /** + * {@inheritDoc} + */ + public function getIterator() + { + return new \ArrayIterator($this); + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Uservoice.php b/vendor/league/oauth1-client/src/Client/Server/Uservoice.php new file mode 100644 index 0000000000..686f7b6551 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Uservoice.php @@ -0,0 +1,130 @@ +parseConfigurationArray($clientCredentials); + } + } + + /** + * {@inheritDoc} + */ + public function urlTemporaryCredentials() + { + return $this->base.'/oauth/request_token'; + } + + /** + * {@inheritDoc} + */ + public function urlAuthorization() + { + return $this->base.'/oauth/authorize'; + } + + /** + * {@inheritDoc} + */ + public function urlTokenCredentials() + { + return $this->base.'/oauth/access_token'; + } + + /** + * {@inheritdoc} + */ + public function urlUserDetails() + { + return $this->base.'/api/v1/users/current.json'; + } + + /** + * {@inheritDoc} + */ + public function userDetails($data, TokenCredentials $tokenCredentials) + { + $user = new User(); + + $user->uid = $data['user']['id']; + $user->name = $data['user']['name']; + $user->imageUrl = $data['user']['avatar_url']; + $user->email = $data['user']['email']; + + if ($data['user']['name']) { + $parts = explode(' ', $data['user']['name']); + + if (count($parts) > 0) { + $user->firstName = $parts[0]; + } + + if (count($parts) > 1) { + $user->lastName = $parts[1]; + } + } + + $user->urls[] = $data['user']['url']; + + return $user; + } + + /** + * {@inheritdoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return $data['user']['id']; + } + + /** + * {@inheritdoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return $data['user']['email']; + } + + /** + * {@inheritdoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return $data['user']['name']; + } + + /** + * Parse configuration array to set attributes. + * + * @param array $configuration + * + * @throws InvalidArgumentException + */ + private function parseConfigurationArray(array $configuration = array()) + { + if (isset($configuration['host'])) { + throw new InvalidArgumentException('Missing host'); + } + + $this->base = trim($configuration['host'], '/'); + } +} diff --git a/vendor/league/oauth1-client/src/Client/Server/Xing.php b/vendor/league/oauth1-client/src/Client/Server/Xing.php new file mode 100644 index 0000000000..8f78157e76 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Server/Xing.php @@ -0,0 +1,92 @@ +uid = $data['id']; + $user->nickname = $data['display_name']; + $user->name = $data['display_name']; + $user->firstName = $data['first_name']; + $user->lastName = $data['last_name']; + $user->location = $data['private_address']['country']; + + if ($user->location == '') { + $user->location = $data['business_address']['country']; + } + $user->description = $data['employment_status']; + $user->imageUrl = $data['photo_urls']['maxi_thumb']; + $user->email = $data['active_email']; + + $user->urls['permalink'] = $data['permalink']; + + return $user; + } + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + $data = $data['users'][0]; + return $data['id']; + } + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + $data = $data['users'][0]; + return $data['active_email']; + } + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + $data = $data['users'][0]; + return $data['display_name']; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Signature/HmacSha1Signature.php b/vendor/league/oauth1-client/src/Client/Signature/HmacSha1Signature.php new file mode 100644 index 0000000000..70fdfc62df --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Signature/HmacSha1Signature.php @@ -0,0 +1,125 @@ +createUrl($uri); + + $baseString = $this->baseString($url, $method, $parameters); + + return base64_encode($this->hash($baseString)); + } + + /** + * Create a Guzzle url for the given URI. + * + * @param string $uri + * + * @return Url + */ + protected function createUrl($uri) + { + return Psr7\uri_for($uri); + } + + /** + * Generate a base string for a HMAC-SHA1 signature + * based on the given a url, method, and any parameters. + * + * @param Url $url + * @param string $method + * @param array $parameters + * + * @return string + */ + protected function baseString(Uri $url, $method = 'POST', array $parameters = array()) + { + $baseString = rawurlencode($method).'&'; + + $schemeHostPath = Uri::fromParts(array( + 'scheme' => $url->getScheme(), + 'host' => $url->getHost(), + 'path' => $url->getPath(), + )); + + $baseString .= rawurlencode($schemeHostPath).'&'; + + $data = array(); + parse_str($url->getQuery(), $query); + $data = array_merge($query, $parameters); + + // normalize data key/values + array_walk_recursive($data, function (&$key, &$value) { + $key = rawurlencode(rawurldecode($key)); + $value = rawurlencode(rawurldecode($value)); + }); + ksort($data); + + $baseString .= $this->queryStringFromData($data); + + return $baseString; + } + + /** + * Creates an array of rawurlencoded strings out of each array key/value pair + * Handles multi-demensional arrays recursively. + * + * @param array $data Array of parameters to convert. + * @param array $queryParams Array to extend. False by default. + * @param string $prevKey Optional Array key to append + * + * @return string rawurlencoded string version of data + */ + protected function queryStringFromData($data, $queryParams = false, $prevKey = '') + { + if ($initial = (false === $queryParams)) { + $queryParams = array(); + } + + foreach ($data as $key => $value) { + if ($prevKey) { + $key = $prevKey.'['.$key.']'; // Handle multi-dimensional array + } + if (is_array($value)) { + $queryParams = $this->queryStringFromData($value, $queryParams, $key); + } else { + $queryParams[] = rawurlencode($key.'='.$value); // join with equals sign + } + } + + if ($initial) { + return implode('%26', $queryParams); // join with ampersand + } + + return $queryParams; + } + + /** + * Hashes a string with the signature's key. + * + * @param string $string + * + * @return string + */ + protected function hash($string) + { + return hash_hmac('sha1', $string, $this->key(), true); + } +} diff --git a/vendor/league/oauth1-client/src/Client/Signature/PlainTextSignature.php b/vendor/league/oauth1-client/src/Client/Signature/PlainTextSignature.php new file mode 100644 index 0000000000..d3ddf9465d --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Signature/PlainTextSignature.php @@ -0,0 +1,22 @@ +key(); + } +} diff --git a/vendor/league/oauth1-client/src/Client/Signature/Signature.php b/vendor/league/oauth1-client/src/Client/Signature/Signature.php new file mode 100644 index 0000000000..e9aa68bf14 --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Signature/Signature.php @@ -0,0 +1,55 @@ +clientCredentials = $clientCredentials; + } + + /** + * {@inheritDoc} + */ + public function setCredentials(CredentialsInterface $credentials) + { + $this->credentials = $credentials; + } + + /** + * Generate a signing key. + * + * @return string + */ + protected function key() + { + $key = rawurlencode($this->clientCredentials->getSecret()).'&'; + + if ($this->credentials !== null) { + $key .= rawurlencode($this->credentials->getSecret()); + } + + return $key; + } +} diff --git a/vendor/league/oauth1-client/src/Client/Signature/SignatureInterface.php b/vendor/league/oauth1-client/src/Client/Signature/SignatureInterface.php new file mode 100644 index 0000000000..69210583cf --- /dev/null +++ b/vendor/league/oauth1-client/src/Client/Signature/SignatureInterface.php @@ -0,0 +1,44 @@ +assertNull($credentials->getIdentifier()); + $credentials->setIdentifier('foo'); + $this->assertEquals('foo', $credentials->getIdentifier()); + $this->assertNull($credentials->getSecret()); + $credentials->setSecret('foo'); + $this->assertEquals('foo', $credentials->getSecret()); + } +} \ No newline at end of file diff --git a/vendor/league/oauth1-client/tests/HmacSha1SignatureTest.php b/vendor/league/oauth1-client/tests/HmacSha1SignatureTest.php new file mode 100644 index 0000000000..6e70f902b4 --- /dev/null +++ b/vendor/league/oauth1-client/tests/HmacSha1SignatureTest.php @@ -0,0 +1,164 @@ +getMockClientCredentials()); + + $uri = 'http://www.example.com/?qux=corge'; + $parameters = array('foo' => 'bar', 'baz' => null); + + $this->assertEquals('A3Y7C1SUHXR1EBYIUlT3d6QT1cQ=', $signature->sign($uri, $parameters)); + } + + public function testQueryStringFromArray() + { + $array = array('a' => 'b'); + $res = $this->invokeQueryStringFromData($array); + + $this->assertSame( + 'a%3Db', + $res + ); + } + + public function testQueryStringFromIndexedArray() + { + $array = array('a', 'b'); + $res = $this->invokeQueryStringFromData($array); + + $this->assertSame( + '0%3Da%261%3Db', + $res + ); + } + + public function testQueryStringFromMultiDimensionalArray() + { + $array = array( + 'a' => array( + 'b' => array( + 'c' => 'd', + ), + 'e' => array( + 'f' => 'g', + ), + ), + 'h' => 'i', + 'empty' => '', + 'null' => null, + 'false' => false, + ); + + // Convert to query string. + $res = $this->invokeQueryStringFromData($array); + + $this->assertSame( + 'a%5Bb%5D%5Bc%5D%3Dd%26a%5Be%5D%5Bf%5D%3Dg%26h%3Di%26empty%3D%26null%3D%26false%3D', + $res + ); + + // Reverse engineer the string. + $res = urldecode($res); + + $this->assertSame( + 'a[b][c]=d&a[e][f]=g&h=i&empty=&null=&false=', + $res + ); + + // Finally, parse the string back to an array. + parse_str($res, $original_array); + + // And ensure it matches the orignal array (approximately). + $this->assertSame( + array( + 'a' => array( + 'b' => array( + 'c' => 'd', + ), + 'e' => array( + 'f' => 'g', + ), + ), + 'h' => 'i', + 'empty' => '', + 'null' => '', // null value gets lost in string translation + 'false' => '', // false value gets lost in string translation + ), + $original_array + ); + } + + public function testSigningRequestWithMultiDimensionalParams() + { + $signature = new HmacSha1Signature($this->getMockClientCredentials()); + + $uri = 'http://www.example.com/'; + $parameters = array( + 'a' => array( + 'b' => array( + 'c' => 'd', + ), + 'e' => array( + 'f' => 'g', + ), + ), + 'h' => 'i', + 'empty' => '', + 'null' => null, + 'false' => false, + ); + + $this->assertEquals('ZUxiJKugeEplaZm9e4hshN0I70U=', $signature->sign($uri, $parameters)); + } + + protected function invokeQueryStringFromData(array $args) + { + $signature = new HmacSha1Signature(m::mock('League\OAuth1\Client\Credentials\ClientCredentialsInterface')); + $refl = new \ReflectionObject($signature); + $method = $refl->getMethod('queryStringFromData'); + $method->setAccessible(true); + return $method->invokeArgs($signature, array($args)); + } + + protected function getMockClientCredentials() + { + $clientCredentials = m::mock('League\OAuth1\Client\Credentials\ClientCredentialsInterface'); + $clientCredentials->shouldReceive('getSecret')->andReturn('clientsecret'); + return $clientCredentials; + } +} diff --git a/vendor/league/oauth1-client/tests/PlainTextSignatureTest.php b/vendor/league/oauth1-client/tests/PlainTextSignatureTest.php new file mode 100644 index 0000000000..3c34e00e9a --- /dev/null +++ b/vendor/league/oauth1-client/tests/PlainTextSignatureTest.php @@ -0,0 +1,60 @@ +getMockClientCredentials()); + $this->assertEquals('clientsecret&', $signature->sign($uri = 'http://www.example.com/')); + + $signature->setCredentials($this->getMockCredentials()); + $this->assertEquals('clientsecret&tokensecret', $signature->sign($uri)); + $this->assertEquals('PLAINTEXT', $signature->method()); + } + + protected function getMockClientCredentials() + { + $clientCredentials = m::mock('League\OAuth1\Client\Credentials\ClientCredentialsInterface'); + $clientCredentials->shouldReceive('getSecret')->andReturn('clientsecret'); + return $clientCredentials; + } + + protected function getMockCredentials() + { + $credentials = m::mock('League\OAuth1\Client\Credentials\CredentialsInterface'); + $credentials->shouldReceive('getSecret')->andReturn('tokensecret'); + return $credentials; + } +} diff --git a/vendor/league/oauth1-client/tests/ServerTest.php b/vendor/league/oauth1-client/tests/ServerTest.php new file mode 100644 index 0000000000..75b93d172a --- /dev/null +++ b/vendor/league/oauth1-client/tests/ServerTest.php @@ -0,0 +1,285 @@ +getMockClientCredentials()); + + $credentials = $server->getClientCredentials(); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\ClientCredentialsInterface', $credentials); + $this->assertEquals('myidentifier', $credentials->getIdentifier()); + $this->assertEquals('mysecret', $credentials->getSecret()); + $this->assertEquals('http://app.dev/', $credentials->getCallbackUri()); + } + + public function testCreatingWithObject() + { + $credentials = new ClientCredentials; + $credentials->setIdentifier('myidentifier'); + $credentials->setSecret('mysecret'); + $credentials->setCallbackUri('http://app.dev/'); + + $server = new ServerStub($credentials); + + $this->assertEquals($credentials, $server->getClientCredentials()); + } + + /** + * @expectedException InvalidArgumentException + **/ + public function testCreatingWithInvalidInput() + { + $server = new ServerStub(uniqid()); + } + + public function testGettingTemporaryCredentials() + { + $server = m::mock('League\OAuth1\Client\Tests\ServerStub[createHttpClient]', array($this->getMockClientCredentials())); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('http://www.example.com/temporary', m::on(function($options) use ($me) { + $headers = $options['headers']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_callback="'.preg_quote('http%3A%2F%2Fapp.dev%2F', '/').'", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=temporarycredentialsidentifier&oauth_token_secret=temporarycredentialssecret&oauth_callback_confirmed=true'); + + $credentials = $server->getTemporaryCredentials(); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TemporaryCredentials', $credentials); + $this->assertEquals('temporarycredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('temporarycredentialssecret', $credentials->getSecret()); + } + + public function testGettingAuthorizationUrl() + { + $server = new ServerStub($this->getMockClientCredentials()); + + $expected = 'http://www.example.com/authorize?oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGettingTokenCredentialsFailsWithManInTheMiddle() + { + $server = new ServerStub($this->getMockClientCredentials()); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + + $server->getTokenCredentials($credentials, 'bar', 'verifier'); + } + + public function testGettingTokenCredentials() + { + $server = m::mock('League\OAuth1\Client\Tests\ServerStub[createHttpClient]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('temporarycredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('temporarycredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('http://www.example.com/token', m::on(function($options) use ($me) { + $headers = $options['headers']; + $body = $options['form_params']; + + $me->assertTrue(isset($headers['Authorization'])); + $me->assertFalse(isset($headers['User-Agent'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="temporarycredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + $me->assertSame($body, array('oauth_verifier' => 'myverifiercode')); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=tokencredentialsidentifier&oauth_token_secret=tokencredentialssecret'); + + $credentials = $server->getTokenCredentials($temporaryCredentials, 'temporarycredentialsidentifier', 'myverifiercode'); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TokenCredentials', $credentials); + $this->assertEquals('tokencredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('tokencredentialssecret', $credentials->getSecret()); + } + + public function testGettingTokenCredentialsWithUserAgent() + { + $userAgent = 'FooBar'; + $server = m::mock('League\OAuth1\Client\Tests\ServerStub[createHttpClient]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('temporarycredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('temporarycredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('http://www.example.com/token', m::on(function($options) use ($me, $userAgent) { + $headers = $options['headers']; + $body = $options['form_params']; + + $me->assertTrue(isset($headers['Authorization'])); + $me->assertTrue(isset($headers['User-Agent'])); + $me->assertEquals($userAgent, $headers['User-Agent']); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="temporarycredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + $me->assertSame($body, array('oauth_verifier' => 'myverifiercode')); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=tokencredentialsidentifier&oauth_token_secret=tokencredentialssecret'); + + $credentials = $server->setUserAgent($userAgent)->getTokenCredentials($temporaryCredentials, 'temporarycredentialsidentifier', 'myverifiercode'); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TokenCredentials', $credentials); + $this->assertEquals('tokencredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('tokencredentialssecret', $credentials->getSecret()); + + } + + public function testGettingUserDetails() + { + $server = m::mock('League\OAuth1\Client\Tests\ServerStub[createHttpClient,protocolHeader]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TokenCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('tokencredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('tokencredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('get')->with('http://www.example.com/user', m::on(function($options) use ($me) { + $headers = $options['headers']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="tokencredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->once()->andReturn(json_encode(array('foo' => 'bar', 'id' => 123, 'contact_email' => 'baz@qux.com', 'username' => 'fred'))); + + $user = $server->getUserDetails($temporaryCredentials); + $this->assertInstanceOf('League\OAuth1\Client\Server\User', $user); + $this->assertEquals('bar', $user->firstName); + $this->assertEquals(123, $server->getUserUid($temporaryCredentials)); + $this->assertEquals('baz@qux.com', $server->getUserEmail($temporaryCredentials)); + $this->assertEquals('fred', $server->getUserScreenName($temporaryCredentials)); + } + + public function testGettingHeaders() + { + $server = new ServerStub($this->getMockClientCredentials()); + + $tokenCredentials = m::mock('League\OAuth1\Client\Credentials\TokenCredentials'); + $tokenCredentials->shouldReceive('getIdentifier')->andReturn('mock_identifier'); + $tokenCredentials->shouldReceive('getSecret')->andReturn('mock_secret'); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="mock_identifier", oauth_signature=".*?"/'; + + // With a GET request + $headers = $server->getHeaders($tokenCredentials, 'GET', 'http://example.com/'); + $this->assertTrue(isset($headers['Authorization'])); + + $matches = preg_match($pattern, $headers['Authorization']); + $this->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + // With a POST request + $headers = $server->getHeaders($tokenCredentials, 'POST', 'http://example.com/', array('body' => 'params')); + $this->assertTrue(isset($headers['Authorization'])); + + $matches = preg_match($pattern, $headers['Authorization']); + $this->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + } + + protected function getMockClientCredentials() + { + return array( + 'identifier' => 'myidentifier', + 'secret' => 'mysecret', + 'callback_uri' => 'http://app.dev/', + ); + } +} diff --git a/vendor/league/oauth1-client/tests/TrelloServerTest.php b/vendor/league/oauth1-client/tests/TrelloServerTest.php new file mode 100644 index 0000000000..1e3105a6d9 --- /dev/null +++ b/vendor/league/oauth1-client/tests/TrelloServerTest.php @@ -0,0 +1,349 @@ +getMockClientCredentials()); + + $credentials = $server->getClientCredentials(); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\ClientCredentialsInterface', $credentials); + $this->assertEquals($this->getApplicationKey(), $credentials->getIdentifier()); + $this->assertEquals('mysecret', $credentials->getSecret()); + $this->assertEquals('http://app.dev/', $credentials->getCallbackUri()); + } + + public function testCreatingWithObject() + { + $credentials = new ClientCredentials; + $credentials->setIdentifier('myidentifier'); + $credentials->setSecret('mysecret'); + $credentials->setCallbackUri('http://app.dev/'); + + $server = new Trello($credentials); + + $this->assertEquals($credentials, $server->getClientCredentials()); + } + + public function testGettingTemporaryCredentials() + { + $server = m::mock('League\OAuth1\Client\Server\Trello[createHttpClient]', array($this->getMockClientCredentials())); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('https://trello.com/1/OAuthGetRequestToken', m::on(function($options) use ($me) { + $headers = $options['headers']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_callback="'.preg_quote('http%3A%2F%2Fapp.dev%2F', '/').'", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=temporarycredentialsidentifier&oauth_token_secret=temporarycredentialssecret&oauth_callback_confirmed=true'); + + $credentials = $server->getTemporaryCredentials(); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TemporaryCredentials', $credentials); + $this->assertEquals('temporarycredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('temporarycredentialssecret', $credentials->getSecret()); + } + + public function testGettingDefaultAuthorizationUrl() + { + $server = new Trello($this->getMockClientCredentials()); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope=read&expiration=1day&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + public function testGettingAuthorizationUrlWithExpirationAfterConstructingWithExpiration() + { + $credentials = $this->getMockClientCredentials(); + $expiration = $this->getApplicationExpiration(2); + $credentials['expiration'] = $expiration; + $server = new Trello($credentials); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope=read&expiration='.urlencode($expiration).'&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + public function testGettingAuthorizationUrlWithExpirationAfterSettingExpiration() + { + $expiration = $this->getApplicationExpiration(2); + $server = new Trello($this->getMockClientCredentials()); + $server->setApplicationExpiration($expiration); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope=read&expiration='.urlencode($expiration).'&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + public function testGettingAuthorizationUrlWithNameAfterConstructingWithName() + { + $credentials = $this->getMockClientCredentials(); + $name = $this->getApplicationName(); + $credentials['name'] = $name; + $server = new Trello($credentials); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope=read&expiration=1day&name='.urlencode($name).'&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + public function testGettingAuthorizationUrlWithNameAfterSettingName() + { + $name = $this->getApplicationName(); + $server = new Trello($this->getMockClientCredentials()); + $server->setApplicationName($name); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope=read&expiration=1day&name='.urlencode($name).'&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + public function testGettingAuthorizationUrlWithScopeAfterConstructingWithScope() + { + $credentials = $this->getMockClientCredentials(); + $scope = $this->getApplicationScope(false); + $credentials['scope'] = $scope; + $server = new Trello($credentials); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope='.urlencode($scope).'&expiration=1day&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + public function testGettingAuthorizationUrlWithScopeAfterSettingScope() + { + $scope = $this->getApplicationScope(false); + $server = new Trello($this->getMockClientCredentials()); + $server->setApplicationScope($scope); + + $expected = 'https://trello.com/1/OAuthAuthorizeToken?response_type=fragment&scope='.urlencode($scope).'&expiration=1day&oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGettingTokenCredentialsFailsWithManInTheMiddle() + { + $server = new Trello($this->getMockClientCredentials()); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + + $server->getTokenCredentials($credentials, 'bar', 'verifier'); + } + + public function testGettingTokenCredentials() + { + $server = m::mock('League\OAuth1\Client\Server\Trello[createHttpClient]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('temporarycredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('temporarycredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('https://trello.com/1/OAuthGetAccessToken', m::on(function($options) use ($me) { + $headers = $options['headers']; + $body = $options['form_params']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="temporarycredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + $me->assertSame($body, array('oauth_verifier' => 'myverifiercode')); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=tokencredentialsidentifier&oauth_token_secret=tokencredentialssecret'); + + $credentials = $server->getTokenCredentials($temporaryCredentials, 'temporarycredentialsidentifier', 'myverifiercode'); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TokenCredentials', $credentials); + $this->assertEquals('tokencredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('tokencredentialssecret', $credentials->getSecret()); + } + + public function testGettingUserDetails() + { + $server = m::mock('League\OAuth1\Client\Server\Trello[createHttpClient,protocolHeader]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TokenCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('tokencredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('tokencredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('get')->with('https://trello.com/1/members/me?key='.$this->getApplicationKey().'&token='.$this->getAccessToken(), m::on(function($options) use ($me) { + $headers = $options['headers']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="tokencredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->once()->andReturn($this->getUserPayload()); + + $user = $server + ->setAccessToken($this->getAccessToken()) + ->getUserDetails($temporaryCredentials); + $this->assertInstanceOf('League\OAuth1\Client\Server\User', $user); + $this->assertEquals('Matilda Wormwood', $user->name); + $this->assertEquals('545df696e29c0dddaed31967', $server->getUserUid($temporaryCredentials)); + $this->assertEquals(null, $server->getUserEmail($temporaryCredentials)); + $this->assertEquals('matildawormwood12', $server->getUserScreenName($temporaryCredentials)); + } + + protected function getMockClientCredentials() + { + return array( + 'identifier' => $this->getApplicationKey(), + 'secret' => 'mysecret', + 'callback_uri' => 'http://app.dev/', + ); + } + + protected function getAccessToken() + { + return 'lmnopqrstuvwxyz'; + } + + protected function getApplicationKey() + { + return 'abcdefghijk'; + } + + protected function getApplicationExpiration($days = 0) + { + return is_numeric($days) && $days > 0 ? $days.'day'.($days == 1 ? '' : 's') : 'never'; + } + + protected function getApplicationName() + { + return 'fizz buzz'; + } + + protected function getApplicationScope($readonly = true) + { + return $readonly ? 'read' : 'read,write'; + } + + private function getUserPayload() + { + return '{ + "id": "545df696e29c0dddaed31967", + "avatarHash": null, + "bio": "I have magical powers", + "bioData": null, + "confirmed": true, + "fullName": "Matilda Wormwood", + "idPremOrgsAdmin": [], + "initials": "MW", + "memberType": "normal", + "products": [], + "status": "idle", + "url": "https://trello.com/matildawormwood12", + "username": "matildawormwood12", + "avatarSource": "none", + "email": null, + "gravatarHash": "39aaaada0224f26f0bb8f1965326dcb7", + "idBoards": [ + "545df696e29c0dddaed31968", + "545e01d6c7b2dd962b5b46cb" + ], + "idOrganizations": [ + "54adfd79f9aea14f84009a85", + "54adfde13b0e706947bc4789" + ], + "loginTypes": null, + "oneTimeMessagesDismissed": [], + "prefs": { + "sendSummaries": true, + "minutesBetweenSummaries": 1, + "minutesBeforeDeadlineToNotify": 1440, + "colorBlind": false, + "timezoneInfo": { + "timezoneNext": "CDT", + "dateNext": "2015-03-08T08:00:00.000Z", + "offsetNext": 300, + "timezoneCurrent": "CST", + "offsetCurrent": 360 + } + }, + "trophies": [], + "uploadedAvatarHash": null, + "premiumFeatures": [], + "idBoardsPinned": null + }'; + } +} diff --git a/vendor/league/oauth1-client/tests/XingServerTest.php b/vendor/league/oauth1-client/tests/XingServerTest.php new file mode 100644 index 0000000000..d3dd0f0040 --- /dev/null +++ b/vendor/league/oauth1-client/tests/XingServerTest.php @@ -0,0 +1,255 @@ +getMockClientCredentials()); + + $credentials = $server->getClientCredentials(); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\ClientCredentialsInterface', $credentials); + $this->assertEquals($this->getApplicationKey(), $credentials->getIdentifier()); + $this->assertEquals('mysecret', $credentials->getSecret()); + $this->assertEquals('http://app.dev/', $credentials->getCallbackUri()); + } + + public function testCreatingWithObject() + { + $credentials = new ClientCredentials; + $credentials->setIdentifier('myidentifier'); + $credentials->setSecret('mysecret'); + $credentials->setCallbackUri('http://app.dev/'); + + $server = new Xing($credentials); + + $this->assertEquals($credentials, $server->getClientCredentials()); + } + + public function testGettingTemporaryCredentials() + { + $server = m::mock('League\OAuth1\Client\Server\Xing[createHttpClient]', array($this->getMockClientCredentials())); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('https://api.xing.com/v1/request_token', m::on(function ($options) use ($me) { + $headers = $options['headers']; + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_callback="'.preg_quote('http%3A%2F%2Fapp.dev%2F', '/').'", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=temporarycredentialsidentifier&oauth_token_secret=temporarycredentialssecret&oauth_callback_confirmed=true'); + + $credentials = $server->getTemporaryCredentials(); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TemporaryCredentials', $credentials); + $this->assertEquals('temporarycredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('temporarycredentialssecret', $credentials->getSecret()); + } + + public function testGettingDefaultAuthorizationUrl() + { + $server = new Xing($this->getMockClientCredentials()); + + $expected = 'https://api.xing.com/v1/authorize?oauth_token=foo'; + + $this->assertEquals($expected, $server->getAuthorizationUrl('foo')); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + $this->assertEquals($expected, $server->getAuthorizationUrl($credentials)); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGettingTokenCredentialsFailsWithManInTheMiddle() + { + $server = new Xing($this->getMockClientCredentials()); + + $credentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $credentials->shouldReceive('getIdentifier')->andReturn('foo'); + + $server->getTokenCredentials($credentials, 'bar', 'verifier'); + } + + public function testGettingTokenCredentials() + { + $server = m::mock('League\OAuth1\Client\Server\Xing[createHttpClient]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TemporaryCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('temporarycredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('temporarycredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('post')->with('https://api.xing.com/v1/access_token', m::on(function ($options) use ($me) { + $headers = $options['headers']; + $body = $options['form_params']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="temporarycredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + $me->assertSame($body, array('oauth_verifier' => 'myverifiercode')); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->andReturn('oauth_token=tokencredentialsidentifier&oauth_token_secret=tokencredentialssecret'); + + $credentials = $server->getTokenCredentials($temporaryCredentials, 'temporarycredentialsidentifier', 'myverifiercode'); + $this->assertInstanceOf('League\OAuth1\Client\Credentials\TokenCredentials', $credentials); + $this->assertEquals('tokencredentialsidentifier', $credentials->getIdentifier()); + $this->assertEquals('tokencredentialssecret', $credentials->getSecret()); + } + + public function testGettingUserDetails() + { + $server = m::mock('League\OAuth1\Client\Server\Xing[createHttpClient,protocolHeader]', array($this->getMockClientCredentials())); + + $temporaryCredentials = m::mock('League\OAuth1\Client\Credentials\TokenCredentials'); + $temporaryCredentials->shouldReceive('getIdentifier')->andReturn('tokencredentialsidentifier'); + $temporaryCredentials->shouldReceive('getSecret')->andReturn('tokencredentialssecret'); + + $server->shouldReceive('createHttpClient')->andReturn($client = m::mock('stdClass')); + + $me = $this; + $client->shouldReceive('get')->with('https://api.xing.com/v1/users/me', m::on(function ($options) use ($me) { + $headers = $options['headers']; + + $me->assertTrue(isset($headers['Authorization'])); + + // OAuth protocol specifies a strict number of + // headers should be sent, in the correct order. + // We'll validate that here. + $pattern = '/OAuth oauth_consumer_key=".*?", oauth_nonce="[a-zA-Z0-9]+", oauth_signature_method="HMAC-SHA1", oauth_timestamp="\d{10}", oauth_version="1.0", oauth_token="tokencredentialsidentifier", oauth_signature=".*?"/'; + + $matches = preg_match($pattern, $headers['Authorization']); + $me->assertEquals(1, $matches, 'Asserting that the authorization header contains the correct expression.'); + + return true; + }))->once()->andReturn($response = m::mock('stdClass')); + $response->shouldReceive('getBody')->once()->andReturn($this->getUserPayload()); + + $user = $server->getUserDetails($temporaryCredentials); + $this->assertInstanceOf('League\OAuth1\Client\Server\User', $user); + $this->assertEquals('Roman Gelembjuk', $user->name); + $this->assertEquals('17144430_0f9409', $server->getUserUid($temporaryCredentials)); + $this->assertEquals('XXXXXXXXXX@gmail.com', $server->getUserEmail($temporaryCredentials)); + $this->assertEquals('Roman Gelembjuk', $server->getUserScreenName($temporaryCredentials)); + } + + protected function getMockClientCredentials() + { + return array( + 'identifier' => $this->getApplicationKey(), + 'secret' => 'mysecret', + 'callback_uri' => 'http://app.dev/', + ); + } + + protected function getApplicationKey() + { + return 'abcdefghijk'; + } + + protected function getApplicationExpiration($days = 0) + { + return is_numeric($days) && $days > 0 ? $days.'day'.($days == 1 ? '' : 's') : 'never'; + } + + protected function getApplicationName() + { + return 'fizz buzz'; + } + + private function getUserPayload() + { + return '{ + "users":[ + { + "id":"17144430_0f9409", + "active_email":"XXXXXXXXXX@gmail.com", + "time_zone": + { + "utc_offset":3.0, + "name":"Europe/Kiev" + }, + "display_name":"Roman Gelembjuk", + "first_name":"Roman", + "last_name":"Gelembjuk", + "gender":"m", + "page_name":"Roman_Gelembjuk", + "birth_date": + {"year":null,"month":null,"day":null}, + "wants":null, + "haves":null, + "interests":null, + "web_profiles":{}, + "badges":[], + "photo_urls": + { + "large":"https://x1.xingassets.com/assets/frontend_minified/img/users/nobody_m.140x185.jpg", + "maxi_thumb":"https://x1.xingassets.com/assets/frontend_minified/img/users/nobody_m.70x93.jpg", + "medium_thumb":"https://x1.xingassets.com/assets/frontend_minified/img/users/nobody_m.57x75.jpg" + }, + "permalink":"https://www.xing.com/profile/Roman_Gelembjuk", + "languages":{"en":null}, + "employment_status":"EMPLOYEE", + "organisation_member":null, + "instant_messaging_accounts":{}, + "educational_background": + {"degree":null,"primary_school":null,"schools":[],"qualifications":[]}, + "private_address":{ + "street":null, + "zip_code":null, + "city":null, + "province":null, + "country":null, + "email":"XXXXXXXX@gmail.com", + "fax":null, + "phone":null, + "mobile_phone":null} + ,"business_address": + { + "street":null, + "zip_code":null, + "city":"Ivano-Frankivsk", + "province":null, + "country":"UA", + "email":null, + "fax":null,"phone":null,"mobile_phone":null + }, + "premium_services":[] + }]}'; + } +} diff --git a/vendor/league/oauth1-client/tests/stubs/ServerStub.php b/vendor/league/oauth1-client/tests/stubs/ServerStub.php new file mode 100644 index 0000000000..b211bab92a --- /dev/null +++ b/vendor/league/oauth1-client/tests/stubs/ServerStub.php @@ -0,0 +1,76 @@ +firstName = $data['foo']; + return $user; + } + + /** + * {@inheritDoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return isset($data['id']) ? $data['id'] : null; + } + + /** + * {@inheritDoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return isset($data['contact_email']) ? $data['contact_email'] : null; + } + + /** + * {@inheritDoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return isset($data['username']) ? $data['username'] : null; + } +} diff --git a/vendor/league/oauth2-client/CHANGELOG.md b/vendor/league/oauth2-client/CHANGELOG.md new file mode 100644 index 0000000000..73a24dd886 --- /dev/null +++ b/vendor/league/oauth2-client/CHANGELOG.md @@ -0,0 +1,301 @@ +# OAuth 2.0 Client Changelog + +## 2.3.0 + +_Released: 2018-01-13_ + +* Add `ProviderRedirectTrait` tool for 3rd-party provider libraries to use when + handling provider redirections +* Fix TypeError thrown because `getResourceOwner()` receives a non-JSON Response +* Gracefully handle non-standard errors received from providers +* Update README to reflect official support of PHP 7.2 + +## 2.2.1 + +_Released: 2017-04-25_ + +* Fix potential type error when HTTP 500 errors are encountered +* Allow broader range of `random_compat` versions + +## 2.2.0 + +_Released: 2017-02-01_ + +* Allow base URLs to contain query parameters +* Protect against `+` being improperly encoded in URL parameters +* Remove misleading `state` option from authorization parameters +* Stop generating more random bytes than necessary + +## 2.1.0 + +_Released: 2017-01-24_ + +* Allow `expires_in` with a value of `0` + +## 2.0.0 + +_Released: 2017-01-12_ + +* Rename `getResponse()` to `getParsedResponse()` +* Add `getResponse()` method that returns the unparsed PSR-7 `Response` instance +* Removed `RandomFactory`, switched to native random functions + +## 1.4.1 + +_Released: 2016-04-29_ + +* Add `QueryBuilderTrait` to standardize query string generation. + +## 1.4.0 + +_Released: 2016-04-19_ + +* Add `AccessToken::getValues()` to access additional vendor data provided with tokens. + +## 1.3.0 + +_Released: 2016-02-13_ + +* Enable dynamic parameters being passed into the authorization URL. +* Minor documentation updates. + +## 1.2.0 + +_Released: 2016-01-23_ + +* Add `resource_owner_id` to the JSON-serialized representation of the access token. +* Minor documentation updates and improved test coverage. + +## 1.1.0 + +_Released: 2015-11-13_ + +* Add `ArrayAccessorTrait`, update `AbstractProvider` to utilize. +* Use `expires` to serialize access tokens. +* Documentation updates. + +## 1.0.2 + +_Released: 2015-09-22_ + +* Allow access tokens to be created from storage (see #431). +* Minor fixes and documentation updates. + +## 1.0.1 + +_Released: 2015-08-26_ + +* Allow required parameters checked using the `RequiredParameterTrait` to be set as `false`, `null`, `"0"`, etc. + +## 1.0.0 + +_Released: 2015-08-19_ + +* We are running code-quality builds through Scrutinizer, and we are running unit test builds on the new Travis CI container-based infrastructure. +* Cleaned up code, as recommended by Scrutinizer. +* Documentation updates. + +## 1.0.0-beta2 + +_Released: 2015-08-12_ + +* BREAK: Add toArray() to ResourceOwnerInterface. +* Always attempt to parse responses as JSON and fallback on failure. +* Add dot notation support to access token resource owner ID. +* Use the Bearer authorization header for the generic provider. +* Documentation updates. + +## 1.0.0-beta1 + +_Released: 2015-07-16_ + +* API for 1.0 is now frozen! +* BREAK: Convert all uses of "User" to "ResourceOwner" to more closely match the OAuth 2.0 specification. +* BREAK: Rename `StandardProvider` to `GenericProvider`. +* BREAK: Move access token creation to the `AbstractProvider`. It was previously handled in the `AbstractGrant`. +* FIX: Add `Content-Type` header with value of `application/x-www-form-urlencoded` to the request header when retrieving access tokens. This adheres to the OAuth 2.0 specification and fixes issues where certain OAuth servers expect this header. +* Enhanced `json_encode()` serialization of AccessToken; when using `json_encode()` on an AccessToken, it will return a JSON object with these properties: `access_token`, `refresh_token`, and `expires_in`. + +## 1.0.0-alpha2 + +_Released: 2015-07-04_ + +* BREAK: Renamed `AbstractProvider::ACCESS_TOKEN_METHOD_GET` to `AbstractProvider::METHOD_GET`. +* BREAK: Renamed `AbstractProvider::ACCESS_TOKEN_METHOD_POST` to `AbstractProvider::METHOD_POST`. +* BREAK: Renamed `AbstractProvider::prepareUserDetails()` to `AbstractProvider::createUser()`. +* BREAK: Renamed `AbstractProvider::getUserDetails()` to `AbstractProvider::getUser()`. +* BREAK: Removed `$token` parameter from `AbstractProvider::getDefaultHeaders()`. +* BREAK: Modify `AbstractProvider::getBaseAccessTokenUrl()` to accept a required array of parameters, allowing providers the ability to vary the access token URL, based on the parameters. +* Removed newline characters from MAC Authorization header. +* Documentation updates, notably: + - Moved list of providers to `README.PROVIDERS.md`. + - Moved provider creation notes to `README.PROVIDER-GUIDE.md`. + +## 1.0.0-alpha1 + +_Released: 2015-06-25_ + +This release contains numerous BC breaks from the 0.x series. Please note these breaks and refer to the [upgrade guide](GUIDE-UPGRADING.md). + +* BREAK: Requires PHP 5.5.0 and greater. +* BREAK: All providers have been moved to separate repositories, one for each provider. +* BREAK: All `public` properties have been set as `protected` or `private` and getters/setters have been introduced for access to these properties. +* BREAK: The `Provider\ProviderInterface` has been removed. Please extend from and override `Provider\AbstractProvider`. +* BREAK: The `Entity\User` has been removed. Providers should implement the `Provider\UserInterface` and provide user functionality instead of expecting it in this base library. +* BREAK: The `Grant\GrantInterface` has been removed. Providers needing to provide a new grant type should extend from and override `Grant\AbstractGrant`. +* A generic `Provider\StandardProvider` has been introduced, which may be used as a client to integrate with most OAuth 2.0 compatible servers. +* A `Grant\GrantFactory` has been introduced as a means to register and retrieve singleton grants from a registry. +* Introduced traits for bearer and MAC authorization (`Tool\BearerAuthorizationTrait` and `Tool\MacAuthorizationTrait`), which providers may use to enable these header authorization types. + +## 0.12.1 + +_Released: 2015-06-20_ + +* FIX: Scope separators for LinkedIn and Instagram are now correctly a single space + +## 0.12.0 + +_Released: 2015-06-15_ + +* BREAK: LinkedIn Provider: Default scopes removed from LinkedIn Provider. See "[Managing LinkedIn Scopes](https://github.com/thephpleague/oauth2-client/blob/9cea9864c2e89bce1b922d1e37ba5378b3b0b264/README.md#managing-linkedin-scopes)" in the README for information on how to set scopes. See [#327](https://github.com/thephpleague/oauth2-client/pull/327) and [#307](https://github.com/thephpleague/oauth2-client/pull/307) for details on this change. +* FIX: LinkedIn Provider: A scenario existed in which `publicProfileUrl` was not set, generating a PHP notice; this has been fixed. +* FIX: Instagram Provider: Fixed scope separator. +* Documentation updates and corrections. + + +## 0.11.0 + +_Released: 2015-04-25_ + +* Identity Provider: Better handling of error responses +* Documentation updates + + +## 0.10.1 + +_Released: 2015-04-02_ + +* FIX: Invalid JSON triggering fatal error +* FIX: Sending headers along with auth `getAccessToken()` requests +* Now running Travis CI tests on PHP 7 +* Documentation updates + + +## 0.10.0 + +_Released: 2015-03-10_ + +* Providers: Added `getHeaders()` to ProviderInterface and updated AbstractProvider to provide the method +* Providers: Updated all bundled providers to support new `$authorizationHeader` property +* Identity Provider: Update IDPException to account for empty strings +* Identity Provider: Added `getResponseBody()` method to IDPException +* Documentation updates, minor bug fixes, and coding standards fixes + + +## 0.9.0 + +_Released: 2015-02-24_ + +* Add `AbstractProvider::prepareAccessTokenResult()` to provide additional token response preparation to providers +* Remove custom provider code from AccessToken +* Add links to README for Dropbox and Square providers + + +## 0.8.1 + +_Released: 2015-02-12_ + +* Allow `approval_prompt` to be set by providers. This fixes an issue where some providers have problems if the `approval_prompt` is present in the query string. + + +## 0.8.0 + +_Released: 2015-02-10_ + +* Facebook Provider: Upgrade to Graph API v2.2 +* Google Provider: Add `access_type` parameter for Google authorization URL +* Get a more reliable response body on errors + + +## 0.7.2 + +_Released: 2015-02-03_ + +* GitHub Provider: Fix regression +* Documentation updates + + +## 0.7.1 + +_Released: 2015-01-06_ + +* Google Provider: fixed issue where Google API was not returning the user ID + + +## 0.7.0 + +_Released: 2014-12-29_ + +* Improvements to Provider\AbstractProvider (addition of `userUid()`, `userEmail()`, and `userScreenName()`) +* GitHub Provider: Support for GitHub Enterprise +* GitHub Provider: Methods to allow fetching user email addresses +* Google Provider: Updated scopes and endpoints to remove deprecated values +* Documentation updates, minor bug fixes, and coding standards fixes + + +## 0.6.0 + +_Released: 2014-12-03_ + +* Added ability to specify a redirect handler for providers through use of a callback (see [Provider\AbstractProvider::setRedirectHandler()](https://github.com/thephpleague/oauth2-client/blob/55de45401eaa21f53c0b2414091da6f3b0f3fcb7/src/Provider/AbstractProvider.php#L314-L317)) +* Updated authorize and token URLs for the Microsoft provider; the old URLs had been phased out and were no longer working (see #146) +* Increased test coverage +* Documentation updates, minor bug fixes, and coding standards fixes + + +## 0.5.0 + +_Released: 2014-11-28_ + +* Added `ClientCredentials` and `Password` grants +* Added support for providers to set their own `uid` parameter key name +* Added support for Google's `hd` (hosted domain) parameter +* Added support for providing a custom `state` parameter to the authorization URL +* LinkedIn `pictureUrl` is now an optional response element +* Added Battle.net provider package link to README +* Added Meetup provider package link to README +* Added `.gitattributes` file +* Increased test coverage +* A number of documentation fixes, minor bug fixes, and coding standards fixes + + +## 0.4.0 + +_Released: 2014-10-28_ + +* Added `ProviderInterface` and removed `IdentityProvider`. +* Expose generated state to allow for CSRF validation. +* Renamed `League\OAuth2\Client\Provider\User` to `League\OAuth2\Client\Entity\User`. +* Entity: User: added `gender` and `locale` properties +* Updating logic for populating the token expiration time. + + +## 0.3.0 + +_Released: 2014-04-26_ + +* This release made some huge leaps forward, including 100% unit-coverage and a bunch of new features. + + +## 0.2.0 + +_Released: 2013-05-28_ + +* No release notes available. + + +## 0.1.0 + +_Released: 2013-05-25_ + +* Initial release. diff --git a/vendor/league/oauth2-client/CONTRIBUTING.md b/vendor/league/oauth2-client/CONTRIBUTING.md new file mode 100644 index 0000000000..056e870057 --- /dev/null +++ b/vendor/league/oauth2-client/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-client). + + +## Pull Requests + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. + +- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. + + +## Testing + +The following tests must pass for a build to be considered successful. If contributing, please ensure these pass before submitting a pull request. + +``` bash +$ ./vendor/bin/parallel-lint src test +$ ./vendor/bin/phpunit --coverage-text +$ ./vendor/bin/phpcs src --standard=psr2 -sp +``` + +**Happy coding**! diff --git a/vendor/league/oauth2-client/CREDITS.md b/vendor/league/oauth2-client/CREDITS.md new file mode 100644 index 0000000000..628f49e6e5 --- /dev/null +++ b/vendor/league/oauth2-client/CREDITS.md @@ -0,0 +1,20 @@ +# OAuth 2.0 Client + +## Authors + +Also see . + +### Current Maintainer + +- [Ben Ramsey](https://github.com/ramsey) + +### Contributors + +- [Alex Bilbie](https://github.com/alexbilbie) +- [Ben Corlett](https://github.com/bencorlett) +- [Ben Ramsey](https://github.com/ramsey) +- [James Mills](https://github.com/jamesmills) +- [Phil Sturgeon](https://github.com/philsturgeon) +- [Rudi Theunissen](https://github.com/rtheunissen) +- [Tom Anderson](https://github.com/TomHAnderson) +- [Woody Gilk](https://github.com/shadowhand) diff --git a/vendor/league/oauth2-client/LICENSE b/vendor/league/oauth2-client/LICENSE new file mode 100644 index 0000000000..262729740a --- /dev/null +++ b/vendor/league/oauth2-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2018 Alex Bilbie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/oauth2-client/README.PROVIDER-GUIDE.md b/vendor/league/oauth2-client/README.PROVIDER-GUIDE.md new file mode 100644 index 0000000000..4999d7da14 --- /dev/null +++ b/vendor/league/oauth2-client/README.PROVIDER-GUIDE.md @@ -0,0 +1,96 @@ +# OAuth 2.0 Client + +## Provider Guide + +New providers may be created by copying the layout of an existing package. See +the [list of providers](docs/providers/thirdparty.md) for good examples. + +When choosing a name for your package, please don’t use the `league` vendor +prefix, as this implies that it is officially supported. You should use your own +username as the vendor prefix, and prepend `oauth2-` to the package name to make +it clear that your package works with OAuth2 Client. For example, if your GitHub +username was "santa," and you were implementing the "giftpay" OAuth2 library, a +good name for your composer package would be `santa/oauth2-giftpay`. + +### Implementing your own provider + +If you are working with an oauth2 service not supported out-of-the-box or by an +existing package, it is quite simple to implement your own. Simply extend +[`League\OAuth2\Client\Provider\AbstractProvider`](src/Provider/AbstractProvider.php) +and implement the required abstract methods: + +```php +abstract public function getBaseAuthorizationUrl(); +abstract public function getBaseAccessTokenUrl(array $params); +abstract public function getResourceOwnerDetailsUrl(AccessToken $token); +abstract protected function getDefaultScopes(); +abstract protected function checkResponse(ResponseInterface $response, $data); +abstract protected function createResourceOwner(array $response, AccessToken $token); +``` + +Each of these abstract methods contain a docblock defining their expectations +and typical behavior. Once you have extended this class, you can simply follow +the [usage example in the README](README.md#usage) using your new `Provider`. + +If you wish to use the `Provider` to make authenticated requests to the +service, you will also need to define how you provide the token to the +service. If this is done via headers, you should override this method: + +```php +protected function getAuthorizationHeaders($token = null); +``` + +This package comes with a trait for implementing `Bearer` authorization. +To use this, you just need to include the trait in your `Provider` class: + +```php + 'demoapp', // The client ID assigned to you by the provider + 'clientSecret' => 'demopass', // The client password assigned to you by the provider + 'redirectUri' => 'http://example.com/your-redirect-url/', + 'urlAuthorize' => 'http://brentertainment.com/oauth2/lockdin/authorize', + 'urlAccessToken' => 'http://brentertainment.com/oauth2/lockdin/token', + 'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource' +]); + +// If we don't have an authorization code then get one +if (!isset($_GET['code'])) { + + // Fetch the authorization URL from the provider; this returns the + // urlAuthorize option and generates and applies any necessary parameters + // (e.g. state). + $authorizationUrl = $provider->getAuthorizationUrl(); + + // Get the state generated for you and store it to the session. + $_SESSION['oauth2state'] = $provider->getState(); + + // Redirect the user to the authorization URL. + header('Location: ' . $authorizationUrl); + exit; + +// Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) { + + if (isset($_SESSION['oauth2state'])) { + unset($_SESSION['oauth2state']); + } + + exit('Invalid state'); + +} else { + + try { + + // Try to get an access token using the authorization code grant. + $accessToken = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + // We have an access token, which we may use in authenticated + // requests against the service provider's API. + echo 'Access Token: ' . $accessToken->getToken() . "
    "; + echo 'Refresh Token: ' . $accessToken->getRefreshToken() . "
    "; + echo 'Expired in: ' . $accessToken->getExpires() . "
    "; + echo 'Already expired? ' . ($accessToken->hasExpired() ? 'expired' : 'not expired') . "
    "; + + // Using the access token, we may look up details about the + // resource owner. + $resourceOwner = $provider->getResourceOwner($accessToken); + + var_export($resourceOwner->toArray()); + + // The provider provides a way to get an authenticated API request for + // the service, using the access token; it returns an object conforming + // to Psr\Http\Message\RequestInterface. + $request = $provider->getAuthenticatedRequest( + 'GET', + 'http://brentertainment.com/oauth2/lockdin/resource', + $accessToken + ); + + } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { + + // Failed to get the access token or user details. + exit($e->getMessage()); + + } + +} +``` + +### Refreshing a Token + +Once your application is authorized, you can refresh an expired token using a refresh token rather than going through the entire process of obtaining a brand new token. To do so, simply reuse this refresh token from your data store to request a refresh. + +_This example uses [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. See authorization code example above, for more details._ + +```php +$provider = new \League\OAuth2\Client\Provider\GenericProvider([ + 'clientId' => 'demoapp', // The client ID assigned to you by the provider + 'clientSecret' => 'demopass', // The client password assigned to you by the provider + 'redirectUri' => 'http://example.com/your-redirect-url/', + 'urlAuthorize' => 'http://brentertainment.com/oauth2/lockdin/authorize', + 'urlAccessToken' => 'http://brentertainment.com/oauth2/lockdin/token', + 'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource' +]); + +$existingAccessToken = getAccessTokenFromYourDataStore(); + +if ($existingAccessToken->hasExpired()) { + $newAccessToken = $provider->getAccessToken('refresh_token', [ + 'refresh_token' => $existingAccessToken->getRefreshToken() + ]); + + // Purge old access token and store new access token to your data store. +} +``` + +### Resource Owner Password Credentials Grant + +Some service providers allow you to skip the authorization code step to exchange a user's credentials (username and password) for an access token. This is referred to as the "resource owner password credentials" grant type. + +According to [section 1.3.3](http://tools.ietf.org/html/rfc6749#section-1.3.3) of the OAuth 2.0 standard (emphasis added): + +> The credentials **should only be used when there is a high degree of trust** +> between the resource owner and the client (e.g., the client is part of the +> device operating system or a highly privileged application), and when other +> authorization grant types are not available (such as an authorization code). + +**We do not advise using this grant type if the service provider supports the authorization code grant type (see above), as this reinforces the [password anti-pattern](https://agentile.com/the-password-anti-pattern) by allowing users to think it's okay to trust third-party applications with their usernames and passwords.** + +That said, there are use-cases where the resource owner password credentials grant is acceptable and useful. Here's an example using it with [Brent Shaffer's](https://github.com/bshaffer) demo OAuth 2.0 application named **Lock'd In**. See authorization code example above, for more details about the Lock'd In demo application. + +``` php +$provider = new \League\OAuth2\Client\Provider\GenericProvider([ + 'clientId' => 'demoapp', // The client ID assigned to you by the provider + 'clientSecret' => 'demopass', // The client password assigned to you by the provider + 'redirectUri' => 'http://example.com/your-redirect-url/', + 'urlAuthorize' => 'http://brentertainment.com/oauth2/lockdin/authorize', + 'urlAccessToken' => 'http://brentertainment.com/oauth2/lockdin/token', + 'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource' +]); + +try { + + // Try to get an access token using the resource owner password credentials grant. + $accessToken = $provider->getAccessToken('password', [ + 'username' => 'demouser', + 'password' => 'testpass' + ]); + +} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { + + // Failed to get the access token + exit($e->getMessage()); + +} +``` + +### Client Credentials Grant + +When your application is acting on its own behalf to access resources it controls/owns in a service provider, it may use the client credentials grant type. This is best used when the credentials for your application are stored privately and never exposed (e.g. through the web browser, etc.) to end-users. This grant type functions similarly to the resource owner password credentials grant type, but it does not request a user's username or password. It uses only the client ID and secret issued to your client by the service provider. + +Unlike earlier examples, the following does not work against a functioning demo service provider. It is provided for the sake of example only. + +``` php +// Note: the GenericProvider requires the `urlAuthorize` option, even though +// it's not used in the OAuth 2.0 client credentials grant type. + +$provider = new \League\OAuth2\Client\Provider\GenericProvider([ + 'clientId' => 'XXXXXX', // The client ID assigned to you by the provider + 'clientSecret' => 'XXXXXX', // The client password assigned to you by the provider + 'redirectUri' => 'http://my.example.com/your-redirect-url/', + 'urlAuthorize' => 'http://service.example.com/authorize', + 'urlAccessToken' => 'http://service.example.com/token', + 'urlResourceOwnerDetails' => 'http://service.example.com/resource' +]); + +try { + + // Try to get an access token using the client credentials grant. + $accessToken = $provider->getAccessToken('client_credentials'); + +} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { + + // Failed to get the access token + exit($e->getMessage()); + +} +``` + +### Using a proxy + +It is possible to use a proxy to debug HTTP calls made to a provider. All you need to do is set the `proxy` and `verify` options when creating your Provider instance. Make sure you enable SSL proxying in your proxy. + +``` php +$provider = new \League\OAuth2\Client\Provider\GenericProvider([ + 'clientId' => 'XXXXXX', // The client ID assigned to you by the provider + 'clientSecret' => 'XXXXXX', // The client password assigned to you by the provider + 'redirectUri' => 'http://my.example.com/your-redirect-url/', + 'urlAuthorize' => 'http://service.example.com/authorize', + 'urlAccessToken' => 'http://service.example.com/token', + 'urlResourceOwnerDetails' => 'http://service.example.com/resource', + 'proxy' => '192.168.0.1:8888', + 'verify' => false +]); +``` + +## Install + +Via Composer + +``` bash +$ composer require league/oauth2-client +``` + +## Contributing + +Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details. + +## License + +The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information. + + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md +[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md diff --git a/vendor/league/oauth2-client/composer.json b/vendor/league/oauth2-client/composer.json new file mode 100644 index 0000000000..66c10045cd --- /dev/null +++ b/vendor/league/oauth2-client/composer.json @@ -0,0 +1,59 @@ +{ + "name": "league/oauth2-client", + "description": "OAuth 2.0 Client Library", + "license": "MIT", + "config": { + "sort-packages": true + }, + "require": { + "php": "^5.6|^7.0", + "guzzlehttp/guzzle": "^6.0", + "paragonie/random_compat": "^1|^2" + }, + "require-dev": { + "eloquent/liberator": "^2.0", + "eloquent/phony-phpunit": "^1.0|^3.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phpunit/phpunit": "^5.7|^6.0", + "squizlabs/php_codesniffer": "^2.3|^3.0" + }, + "keywords": [ + "oauth", + "oauth2", + "authorization", + "authentication", + "idp", + "identity", + "sso", + "single sign on" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + + ], + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + } +} diff --git a/vendor/league/oauth2-client/src/Grant/AbstractGrant.php b/vendor/league/oauth2-client/src/Grant/AbstractGrant.php new file mode 100644 index 0000000000..2c0244ba3d --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/AbstractGrant.php @@ -0,0 +1,80 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Tool\RequiredParameterTrait; + +/** + * Represents a type of authorization grant. + * + * An authorization grant is a credential representing the resource + * owner's authorization (to access its protected resources) used by the + * client to obtain an access token. OAuth 2.0 defines four + * grant types -- authorization code, implicit, resource owner password + * credentials, and client credentials -- as well as an extensibility + * mechanism for defining additional types. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3) + */ +abstract class AbstractGrant +{ + use RequiredParameterTrait; + + /** + * Returns the name of this grant, eg. 'grant_name', which is used as the + * grant type when encoding URL query parameters. + * + * @return string + */ + abstract protected function getName(); + + /** + * Returns a list of all required request parameters. + * + * @return array + */ + abstract protected function getRequiredRequestParameters(); + + /** + * Returns this grant's name as its string representation. This allows for + * string interpolation when building URL query parameters. + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Prepares an access token request's parameters by checking that all + * required parameters are set, then merging with any given defaults. + * + * @param array $defaults + * @param array $options + * @return array + */ + public function prepareRequestParameters(array $defaults, array $options) + { + $defaults['grant_type'] = $this->getName(); + + $required = $this->getRequiredRequestParameters(); + $provided = array_merge($defaults, $options); + + $this->checkRequiredParameters($required, $provided); + + return $provided; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php b/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php new file mode 100644 index 0000000000..c49460c02d --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/AuthorizationCode.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents an authorization code grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1) + */ +class AuthorizationCode extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'authorization_code'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'code', + ]; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/ClientCredentials.php b/vendor/league/oauth2-client/src/Grant/ClientCredentials.php new file mode 100644 index 0000000000..dc78c4fdab --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/ClientCredentials.php @@ -0,0 +1,39 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a client credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4) + */ +class ClientCredentials extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'client_credentials'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return []; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php b/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php new file mode 100644 index 0000000000..c3c4e677b4 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant\Exception; + +use InvalidArgumentException; + +/** + * Exception thrown if the grant does not extend from AbstractGrant. + * + * @see League\OAuth2\Client\Grant\AbstractGrant + */ +class InvalidGrantException extends InvalidArgumentException +{ +} diff --git a/vendor/league/oauth2-client/src/Grant/GrantFactory.php b/vendor/league/oauth2-client/src/Grant/GrantFactory.php new file mode 100644 index 0000000000..71990e83db --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/GrantFactory.php @@ -0,0 +1,104 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +use League\OAuth2\Client\Grant\Exception\InvalidGrantException; + +/** + * Represents a factory used when retrieving an authorization grant type. + */ +class GrantFactory +{ + /** + * @var array + */ + protected $registry = []; + + /** + * Defines a grant singleton in the registry. + * + * @param string $name + * @param AbstractGrant $grant + * @return self + */ + public function setGrant($name, AbstractGrant $grant) + { + $this->registry[$name] = $grant; + + return $this; + } + + /** + * Returns a grant singleton by name. + * + * If the grant has not be registered, a default grant will be loaded. + * + * @param string $name + * @return AbstractGrant + */ + public function getGrant($name) + { + if (empty($this->registry[$name])) { + $this->registerDefaultGrant($name); + } + + return $this->registry[$name]; + } + + /** + * Registers a default grant singleton by name. + * + * @param string $name + * @return self + */ + protected function registerDefaultGrant($name) + { + // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode' + $class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + $class = 'League\\OAuth2\\Client\\Grant\\' . $class; + + $this->checkGrant($class); + + return $this->setGrant($name, new $class); + } + + /** + * Determines if a variable is a valid grant. + * + * @param mixed $class + * @return boolean + */ + public function isGrant($class) + { + return is_subclass_of($class, AbstractGrant::class); + } + + /** + * Checks if a variable is a valid grant. + * + * @throws InvalidGrantException + * @param mixed $class + * @return void + */ + public function checkGrant($class) + { + if (!$this->isGrant($class)) { + throw new InvalidGrantException(sprintf( + 'Grant "%s" must extend AbstractGrant', + is_object($class) ? get_class($class) : $class + )); + } + } +} diff --git a/vendor/league/oauth2-client/src/Grant/Password.php b/vendor/league/oauth2-client/src/Grant/Password.php new file mode 100644 index 0000000000..6543b2ebd1 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/Password.php @@ -0,0 +1,42 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a resource owner password credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3) + */ +class Password extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'password'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'username', + 'password', + ]; + } +} diff --git a/vendor/league/oauth2-client/src/Grant/RefreshToken.php b/vendor/league/oauth2-client/src/Grant/RefreshToken.php new file mode 100644 index 0000000000..8192182301 --- /dev/null +++ b/vendor/league/oauth2-client/src/Grant/RefreshToken.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Grant; + +/** + * Represents a refresh token grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6) + */ +class RefreshToken extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'refresh_token'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'refresh_token', + ]; + } +} diff --git a/vendor/league/oauth2-client/src/Provider/AbstractProvider.php b/vendor/league/oauth2-client/src/Provider/AbstractProvider.php new file mode 100644 index 0000000000..0e2e94b0d2 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/AbstractProvider.php @@ -0,0 +1,828 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use GuzzleHttp\Client as HttpClient; +use GuzzleHttp\ClientInterface as HttpClientInterface; +use GuzzleHttp\Exception\BadResponseException; +use League\OAuth2\Client\Grant\AbstractGrant; +use League\OAuth2\Client\Grant\GrantFactory; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Tool\ArrayAccessorTrait; +use League\OAuth2\Client\Tool\QueryBuilderTrait; +use League\OAuth2\Client\Tool\RequestFactory; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use UnexpectedValueException; + +/** + * Represents a service provider (authorization server). + * + * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1) + */ +abstract class AbstractProvider +{ + use ArrayAccessorTrait; + use QueryBuilderTrait; + + /** + * @var string Key used in a token response to identify the resource owner. + */ + const ACCESS_TOKEN_RESOURCE_OWNER_ID = null; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_GET = 'GET'; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_POST = 'POST'; + + /** + * @var string + */ + protected $clientId; + + /** + * @var string + */ + protected $clientSecret; + + /** + * @var string + */ + protected $redirectUri; + + /** + * @var string + */ + protected $state; + + /** + * @var GrantFactory + */ + protected $grantFactory; + + /** + * @var RequestFactory + */ + protected $requestFactory; + + /** + * @var HttpClientInterface + */ + protected $httpClient; + + /** + * Constructs an OAuth 2.0 service provider. + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @param array $collaborators An array of collaborators that may be used to + * override this provider's default behavior. Collaborators include + * `grantFactory`, `requestFactory`, and `httpClient`. + * Individual providers may introduce more collaborators, as needed. + */ + public function __construct(array $options = [], array $collaborators = []) + { + foreach ($options as $option => $value) { + if (property_exists($this, $option)) { + $this->{$option} = $value; + } + } + + if (empty($collaborators['grantFactory'])) { + $collaborators['grantFactory'] = new GrantFactory(); + } + $this->setGrantFactory($collaborators['grantFactory']); + + if (empty($collaborators['requestFactory'])) { + $collaborators['requestFactory'] = new RequestFactory(); + } + $this->setRequestFactory($collaborators['requestFactory']); + + if (empty($collaborators['httpClient'])) { + $client_options = $this->getAllowedClientOptions($options); + + $collaborators['httpClient'] = new HttpClient( + array_intersect_key($options, array_flip($client_options)) + ); + } + $this->setHttpClient($collaborators['httpClient']); + } + + /** + * Returns the list of options that can be passed to the HttpClient + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @return array The options to pass to the HttpClient constructor + */ + protected function getAllowedClientOptions(array $options) + { + $client_options = ['timeout', 'proxy']; + + // Only allow turning off ssl verification if it's for a proxy + if (!empty($options['proxy'])) { + $client_options[] = 'verify'; + } + + return $client_options; + } + + /** + * Sets the grant factory instance. + * + * @param GrantFactory $factory + * @return self + */ + public function setGrantFactory(GrantFactory $factory) + { + $this->grantFactory = $factory; + + return $this; + } + + /** + * Returns the current grant factory instance. + * + * @return GrantFactory + */ + public function getGrantFactory() + { + return $this->grantFactory; + } + + /** + * Sets the request factory instance. + * + * @param RequestFactory $factory + * @return self + */ + public function setRequestFactory(RequestFactory $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Returns the request factory instance. + * + * @return RequestFactory + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + + /** + * Sets the HTTP client instance. + * + * @param HttpClientInterface $client + * @return self + */ + public function setHttpClient(HttpClientInterface $client) + { + $this->httpClient = $client; + + return $this; + } + + /** + * Returns the HTTP client instance. + * + * @return HttpClientInterface + */ + public function getHttpClient() + { + return $this->httpClient; + } + + /** + * Returns the current value of the state parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Returns the base URL for authorizing a client. + * + * Eg. https://oauth.service.com/authorize + * + * @return string + */ + abstract public function getBaseAuthorizationUrl(); + + /** + * Returns the base URL for requesting an access token. + * + * Eg. https://oauth.service.com/token + * + * @param array $params + * @return string + */ + abstract public function getBaseAccessTokenUrl(array $params); + + /** + * Returns the URL for requesting the resource owner's details. + * + * @param AccessToken $token + * @return string + */ + abstract public function getResourceOwnerDetailsUrl(AccessToken $token); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomState($length = 32) + { + // Converting bytes to hex will always double length. Hence, we can reduce + // the amount of bytes by half to produce the correct length. + return bin2hex(random_bytes($length / 2)); + } + + /** + * Returns the default scopes used by this provider. + * + * This should only be the scopes that are required to request the details + * of the resource owner, rather than all the available scopes. + * + * @return array + */ + abstract protected function getDefaultScopes(); + + /** + * Returns the string that should be used to separate scopes when building + * the URL for requesting an access token. + * + * @return string Scope separator, defaults to ',' + */ + protected function getScopeSeparator() + { + return ','; + } + + /** + * Returns authorization parameters based on provided options. + * + * @param array $options + * @return array Authorization parameters + */ + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + + $options += [ + 'response_type' => 'code', + 'approval_prompt' => 'auto' + ]; + + if (is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + + // Store the state as it may need to be accessed later on. + $this->state = $options['state']; + + // Business code layer might set a different redirect_uri parameter + // depending on the context, leave it as-is + if (!isset($options['redirect_uri'])) { + $options['redirect_uri'] = $this->redirectUri; + } + + $options['client_id'] = $this->clientId; + + return $options; + } + + /** + * Builds the authorization URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAuthorizationQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Builds the authorization URL. + * + * @param array $options + * @return string Authorization URL + */ + public function getAuthorizationUrl(array $options = []) + { + $base = $this->getBaseAuthorizationUrl(); + $params = $this->getAuthorizationParameters($options); + $query = $this->getAuthorizationQuery($params); + + return $this->appendQuery($base, $query); + } + + /** + * Redirects the client for authorization. + * + * @param array $options + * @param callable|null $redirectHandler + * @return mixed + */ + public function authorize( + array $options = [], + callable $redirectHandler = null + ) { + $url = $this->getAuthorizationUrl($options); + if ($redirectHandler) { + return $redirectHandler($url, $this); + } + + // @codeCoverageIgnoreStart + header('Location: ' . $url); + exit; + // @codeCoverageIgnoreEnd + } + + /** + * Appends a query string to a URL. + * + * @param string $url The URL to append the query to + * @param string $query The HTTP query string + * @return string The resulting URL + */ + protected function appendQuery($url, $query) + { + $query = trim($query, '?&'); + + if ($query) { + $glue = strstr($url, '?') === false ? '?' : '&'; + return $url . $glue . $query; + } + + return $url; + } + + /** + * Returns the method to use when requesting an access token. + * + * @return string HTTP method + */ + protected function getAccessTokenMethod() + { + return self::METHOD_POST; + } + + /** + * Returns the key used in the access token response to identify the resource owner. + * + * @return string|null Resource owner identifier key + */ + protected function getAccessTokenResourceOwnerId() + { + return static::ACCESS_TOKEN_RESOURCE_OWNER_ID; + } + + /** + * Builds the access token URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAccessTokenQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Checks that a provided grant is valid, or attempts to produce one if the + * provided grant is a string. + * + * @param AbstractGrant|string $grant + * @return AbstractGrant + */ + protected function verifyGrant($grant) + { + if (is_string($grant)) { + return $this->grantFactory->getGrant($grant); + } + + $this->grantFactory->checkGrant($grant); + return $grant; + } + + /** + * Returns the full URL to use when requesting an access token. + * + * @param array $params Query parameters + * @return string + */ + protected function getAccessTokenUrl(array $params) + { + $url = $this->getBaseAccessTokenUrl($params); + + if ($this->getAccessTokenMethod() === self::METHOD_GET) { + $query = $this->getAccessTokenQuery($params); + return $this->appendQuery($url, $query); + } + + return $url; + } + + /** + * Returns the request body for requesting an access token. + * + * @param array $params + * @return string + */ + protected function getAccessTokenBody(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Builds request options used for requesting an access token. + * + * @param array $params + * @return array + */ + protected function getAccessTokenOptions(array $params) + { + $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']]; + + if ($this->getAccessTokenMethod() === self::METHOD_POST) { + $options['body'] = $this->getAccessTokenBody($params); + } + + return $options; + } + + /** + * Returns a prepared request for requesting an access token. + * + * @param array $params Query string parameters + * @return RequestInterface + */ + protected function getAccessTokenRequest(array $params) + { + $method = $this->getAccessTokenMethod(); + $url = $this->getAccessTokenUrl($params); + $options = $this->getAccessTokenOptions($params); + + return $this->getRequest($method, $url, $options); + } + + /** + * Requests an access token using a specified grant and option set. + * + * @param mixed $grant + * @param array $options + * @return AccessToken + */ + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + + $params = [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + ]; + + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + + return $token; + } + + /** + * Returns a PSR-7 request instance that is not authenticated. + * + * @param string $method + * @param string $url + * @param array $options + * @return RequestInterface + */ + public function getRequest($method, $url, array $options = []) + { + return $this->createRequest($method, $url, null, $options); + } + + /** + * Returns an authenticated PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessToken|string $token + * @param array $options Any of "headers", "body", and "protocolVersion". + * @return RequestInterface + */ + public function getAuthenticatedRequest($method, $url, $token, array $options = []) + { + return $this->createRequest($method, $url, $token, $options); + } + + /** + * Creates a PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessToken|string|null $token + * @param array $options + * @return RequestInterface + */ + protected function createRequest($method, $url, $token, array $options) + { + $defaults = [ + 'headers' => $this->getHeaders($token), + ]; + + $options = array_merge_recursive($defaults, $options); + $factory = $this->getRequestFactory(); + + return $factory->getRequestWithOptions($method, $url, $options); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + return $this->getHttpClient()->send($request); + } + + /** + * Sends a request and returns the parsed response. + * + * @param RequestInterface $request + * @return mixed + */ + public function getParsedResponse(RequestInterface $request) + { + try { + $response = $this->getResponse($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + $parsed = $this->parseResponse($response); + + $this->checkResponse($response, $parsed); + + return $parsed; + } + + /** + * Attempts to parse a JSON response. + * + * @param string $content JSON content from response body + * @return array Parsed JSON data + * @throws UnexpectedValueException if the content could not be parsed + */ + protected function parseJson($content) + { + $content = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new UnexpectedValueException(sprintf( + "Failed to parse JSON response: %s", + json_last_error_msg() + )); + } + + return $content; + } + + /** + * Returns the content type header of a response. + * + * @param ResponseInterface $response + * @return string Semi-colon separated join of content-type headers. + */ + protected function getContentType(ResponseInterface $response) + { + return join(';', (array) $response->getHeader('content-type')); + } + + /** + * Parses the response according to its content-type header. + * + * @throws UnexpectedValueException + * @param ResponseInterface $response + * @return array + */ + protected function parseResponse(ResponseInterface $response) + { + $content = (string) $response->getBody(); + $type = $this->getContentType($response); + + if (strpos($type, 'urlencoded') !== false) { + parse_str($content, $parsed); + return $parsed; + } + + // Attempt to parse the string as JSON regardless of content type, + // since some providers use non-standard content types. Only throw an + // exception if the JSON could not be parsed when it was expected to. + try { + return $this->parseJson($content); + } catch (UnexpectedValueException $e) { + if (strpos($type, 'json') !== false) { + throw $e; + } + + if ($response->getStatusCode() == 500) { + throw new UnexpectedValueException( + 'An OAuth server error was encountered that did not contain a JSON body', + 0, + $e + ); + } + + return $content; + } + } + + /** + * Checks a provider response for errors. + * + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param array|string $data Parsed response data + * @return void + */ + abstract protected function checkResponse(ResponseInterface $response, $data); + + /** + * Prepares an parsed access token response for a grant. + * + * Custom mapping of expiration, etc should be done here. Always call the + * parent method when overloading this method. + * + * @param mixed $result + * @return array + */ + protected function prepareAccessTokenResponse(array $result) + { + if ($this->getAccessTokenResourceOwnerId() !== null) { + $result['resource_owner_id'] = $this->getValueByKey( + $result, + $this->getAccessTokenResourceOwnerId() + ); + } + return $result; + } + + /** + * Creates an access token from a response. + * + * The grant that was used to fetch the response can be used to provide + * additional context. + * + * @param array $response + * @param AbstractGrant $grant + * @return AccessToken + */ + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response); + } + + /** + * Generates a resource owner object from a successful resource owner + * details request. + * + * @param array $response + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + abstract protected function createResourceOwner(array $response, AccessToken $token); + + /** + * Requests and returns the resource owner of given access token. + * + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + public function getResourceOwner(AccessToken $token) + { + $response = $this->fetchResourceOwnerDetails($token); + + return $this->createResourceOwner($response, $token); + } + + /** + * Requests resource owner details. + * + * @param AccessToken $token + * @return mixed + */ + protected function fetchResourceOwnerDetails(AccessToken $token) + { + $url = $this->getResourceOwnerDetailsUrl($token); + + $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + + $response = $this->getParsedResponse($request); + + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + + return $response; + } + + /** + * Returns the default headers used by this provider. + * + * Typically this is used to set 'Accept' or 'Content-Type' headers. + * + * @return array + */ + protected function getDefaultHeaders() + { + return []; + } + + /** + * Returns the authorization headers used by this provider. + * + * Typically this is "Bearer" or "MAC". For more information see: + * http://tools.ietf.org/html/rfc6749#section-7.1 + * + * No default is provided, providers must overload this method to activate + * authorization headers. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return []; + } + + /** + * Returns all headers used by this provider for a request. + * + * The request will be authenticated if an access token is provided. + * + * @param mixed|null $token object or string + * @return array + */ + public function getHeaders($token = null) + { + if ($token) { + return array_merge( + $this->getDefaultHeaders(), + $this->getAuthorizationHeaders($token) + ); + } + + return $this->getDefaultHeaders(); + } +} diff --git a/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php b/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php new file mode 100644 index 0000000000..52b7e0353c --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php @@ -0,0 +1,48 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider\Exception; + +/** + * Exception thrown if the provider response contains errors. + */ +class IdentityProviderException extends \Exception +{ + /** + * @var mixed + */ + protected $response; + + /** + * @param string $message + * @param int $code + * @param array|string $response The response body + */ + public function __construct($message, $code, $response) + { + $this->response = $response; + + parent::__construct($message, $code); + } + + /** + * Returns the exception's response body. + * + * @return array|string + */ + public function getResponseBody() + { + return $this->response; + } +} diff --git a/vendor/league/oauth2-client/src/Provider/GenericProvider.php b/vendor/league/oauth2-client/src/Provider/GenericProvider.php new file mode 100644 index 0000000000..74393ffda5 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/GenericProvider.php @@ -0,0 +1,233 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +use InvalidArgumentException; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Tool\BearerAuthorizationTrait; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a generic service provider that may be used to interact with any + * OAuth 2.0 service provider, using Bearer token authentication. + */ +class GenericProvider extends AbstractProvider +{ + use BearerAuthorizationTrait; + + /** + * @var string + */ + private $urlAuthorize; + + /** + * @var string + */ + private $urlAccessToken; + + /** + * @var string + */ + private $urlResourceOwnerDetails; + + /** + * @var string + */ + private $accessTokenMethod; + + /** + * @var string + */ + private $accessTokenResourceOwnerId; + + /** + * @var array|null + */ + private $scopes = null; + + /** + * @var string + */ + private $scopeSeparator; + + /** + * @var string + */ + private $responseError = 'error'; + + /** + * @var string + */ + private $responseCode; + + /** + * @var string + */ + private $responseResourceOwnerId = 'id'; + + /** + * @param array $options + * @param array $collaborators + */ + public function __construct(array $options = [], array $collaborators = []) + { + $this->assertRequiredOptions($options); + + $possible = $this->getConfigurableOptions(); + $configured = array_intersect_key($options, array_flip($possible)); + + foreach ($configured as $key => $value) { + $this->$key = $value; + } + + // Remove all options that are only used locally + $options = array_diff_key($options, $configured); + + parent::__construct($options, $collaborators); + } + + /** + * Returns all options that can be configured. + * + * @return array + */ + protected function getConfigurableOptions() + { + return array_merge($this->getRequiredOptions(), [ + 'accessTokenMethod', + 'accessTokenResourceOwnerId', + 'scopeSeparator', + 'responseError', + 'responseCode', + 'responseResourceOwnerId', + 'scopes', + ]); + } + + /** + * Returns all options that are required. + * + * @return array + */ + protected function getRequiredOptions() + { + return [ + 'urlAuthorize', + 'urlAccessToken', + 'urlResourceOwnerDetails', + ]; + } + + /** + * Verifies that all required options have been passed. + * + * @param array $options + * @return void + * @throws InvalidArgumentException + */ + private function assertRequiredOptions(array $options) + { + $missing = array_diff_key(array_flip($this->getRequiredOptions()), $options); + + if (!empty($missing)) { + throw new InvalidArgumentException( + 'Required options not defined: ' . implode(', ', array_keys($missing)) + ); + } + } + + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl() + { + return $this->urlAuthorize; + } + + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->urlAccessToken; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + return $this->urlResourceOwnerDetails; + } + + /** + * @inheritdoc + */ + public function getDefaultScopes() + { + return $this->scopes; + } + + /** + * @inheritdoc + */ + protected function getAccessTokenMethod() + { + return $this->accessTokenMethod ?: parent::getAccessTokenMethod(); + } + + /** + * @inheritdoc + */ + protected function getAccessTokenResourceOwnerId() + { + return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId(); + } + + /** + * @inheritdoc + */ + protected function getScopeSeparator() + { + return $this->scopeSeparator ?: parent::getScopeSeparator(); + } + + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data[$this->responseError])) { + $error = $data[$this->responseError]; + if (!is_string($error)) { + $error = var_export($error, true); + } + $code = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0; + if (!is_int($code)) { + $code = intval($code); + } + throw new IdentityProviderException($error, $code, $data); + } + } + + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + return new GenericResourceOwner($response, $this->responseResourceOwnerId); + } +} diff --git a/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php b/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php new file mode 100644 index 0000000000..f876614851 --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/GenericResourceOwner.php @@ -0,0 +1,61 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Represents a generic resource owner for use with the GenericProvider. + */ +class GenericResourceOwner implements ResourceOwnerInterface +{ + /** + * @var array + */ + protected $response; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @param array $response + * @param string $resourceOwnerId + */ + public function __construct(array $response, $resourceOwnerId) + { + $this->response = $response; + $this->resourceOwnerId = $resourceOwnerId; + } + + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId() + { + return $this->response[$this->resourceOwnerId]; + } + + /** + * Returns the raw resource owner response. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php b/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php new file mode 100644 index 0000000000..828442425f --- /dev/null +++ b/vendor/league/oauth2-client/src/Provider/ResourceOwnerInterface.php @@ -0,0 +1,36 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Provider; + +/** + * Classes implementing `ResourceOwnerInterface` may be used to represent + * the resource owner authenticated with a service provider. + */ +interface ResourceOwnerInterface +{ + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId(); + + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/vendor/league/oauth2-client/src/Token/AccessToken.php b/vendor/league/oauth2-client/src/Token/AccessToken.php new file mode 100644 index 0000000000..89d693c6f5 --- /dev/null +++ b/vendor/league/oauth2-client/src/Token/AccessToken.php @@ -0,0 +1,228 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +use InvalidArgumentException; +use JsonSerializable; +use RuntimeException; + +/** + * Represents an access token. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4) + */ +class AccessToken implements JsonSerializable +{ + /** + * @var string + */ + protected $accessToken; + + /** + * @var int + */ + protected $expires; + + /** + * @var string + */ + protected $refreshToken; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @var array + */ + protected $values = []; + + /** + * Constructs an access token. + * + * @param array $options An array of options returned by the service provider + * in the access token request. The `access_token` option is required. + * @throws InvalidArgumentException if `access_token` is not provided in `$options`. + */ + public function __construct(array $options = []) + { + if (empty($options['access_token'])) { + throw new InvalidArgumentException('Required option not passed: "access_token"'); + } + + $this->accessToken = $options['access_token']; + + if (!empty($options['resource_owner_id'])) { + $this->resourceOwnerId = $options['resource_owner_id']; + } + + if (!empty($options['refresh_token'])) { + $this->refreshToken = $options['refresh_token']; + } + + // We need to know when the token expires. Show preference to + // 'expires_in' since it is defined in RFC6749 Section 5.1. + // Defer to 'expires' if it is provided instead. + if (isset($options['expires_in'])) { + if (!is_numeric($options['expires_in'])) { + throw new \InvalidArgumentException('expires_in value must be an integer'); + } + + $this->expires = $options['expires_in'] != 0 ? time() + $options['expires_in'] : 0; + } elseif (!empty($options['expires'])) { + // Some providers supply the seconds until expiration rather than + // the exact timestamp. Take a best guess at which we received. + $expires = $options['expires']; + + if (!$this->isExpirationTimestamp($expires)) { + $expires += time(); + } + + $this->expires = $expires; + } + + // Capture any additional values that might exist in the token but are + // not part of the standard response. Vendors will sometimes pass + // additional user data this way. + $this->values = array_diff_key($options, array_flip([ + 'access_token', + 'resource_owner_id', + 'refresh_token', + 'expires_in', + 'expires', + ])); + } + + /** + * Check if a value is an expiration timestamp or second value. + * + * @param integer $value + * @return bool + */ + protected function isExpirationTimestamp($value) + { + // If the given value is larger than the original OAuth 2 draft date, + // assume that it is meant to be a (possible expired) timestamp. + $oauth2InceptionDate = 1349067600; // 2012-10-01 + return ($value > $oauth2InceptionDate); + } + + /** + * Returns the access token string of this instance. + * + * @return string + */ + public function getToken() + { + return $this->accessToken; + } + + /** + * Returns the refresh token, if defined. + * + * @return string|null + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * Returns the expiration timestamp, if defined. + * + * @return integer|null + */ + public function getExpires() + { + return $this->expires; + } + + /** + * Returns the resource owner identifier, if defined. + * + * @return string|null + */ + public function getResourceOwnerId() + { + return $this->resourceOwnerId; + } + + /** + * Checks if this token has expired. + * + * @return boolean true if the token has expired, false otherwise. + * @throws RuntimeException if 'expires' is not set on the token. + */ + public function hasExpired() + { + $expires = $this->getExpires(); + + if (empty($expires)) { + throw new RuntimeException('"expires" is not set on the token'); + } + + return $expires < time(); + } + + /** + * Returns additional vendor values stored in the token. + * + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * Returns the token key. + * + * @return string + */ + public function __toString() + { + return (string) $this->getToken(); + } + + /** + * Returns an array of parameters to serialize when this is serialized with + * json_encode(). + * + * @return array + */ + public function jsonSerialize() + { + $parameters = $this->values; + + if ($this->accessToken) { + $parameters['access_token'] = $this->accessToken; + } + + if ($this->refreshToken) { + $parameters['refresh_token'] = $this->refreshToken; + } + + if ($this->expires) { + $parameters['expires'] = $this->expires; + } + + if ($this->resourceOwnerId) { + $parameters['resource_owner_id'] = $this->resourceOwnerId; + } + + return $parameters; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php b/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php new file mode 100644 index 0000000000..a18198cf30 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/ArrayAccessorTrait.php @@ -0,0 +1,52 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides generic array navigation tools. + */ +trait ArrayAccessorTrait +{ + /** + * Returns a value by key using dot notation. + * + * @param array $data + * @param string $key + * @param mixed|null $default + * @return mixed + */ + private function getValueByKey(array $data, $key, $default = null) + { + if (!is_string($key) || empty($key) || !count($data)) { + return $default; + } + + if (strpos($key, '.') !== false) { + $keys = explode('.', $key); + + foreach ($keys as $innerKey) { + if (!is_array($data) || !array_key_exists($innerKey, $data)) { + return $default; + } + + $data = $data[$innerKey]; + } + + return $data; + } + + return array_key_exists($key, $data) ? $data[$key] : $default; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php b/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php new file mode 100644 index 0000000000..967cca868c --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php @@ -0,0 +1,34 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Enables `Bearer` header authorization for providers. + * + * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750) + */ +trait BearerAuthorizationTrait +{ + /** + * Returns authorization headers for the 'bearer' grant. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return ['Authorization' => 'Bearer ' . $token]; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php b/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php new file mode 100644 index 0000000000..d36c00ad61 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/MacAuthorizationTrait.php @@ -0,0 +1,78 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use League\OAuth2\Client\Token\AccessToken; + +/** + * Enables `MAC` header authorization for providers. + * + * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens + */ +trait MacAuthorizationTrait +{ + /** + * Returns the id of this token for MAC generation. + * + * @param AccessToken $token + * @return string + */ + abstract protected function getTokenId(AccessToken $token); + + /** + * Returns the MAC signature for the current request. + * + * @param string $id + * @param integer $ts + * @param string $nonce + * @return string + */ + abstract protected function getMacSignature($id, $ts, $nonce); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + abstract protected function getRandomState($length); + + /** + * Returns the authorization headers for the 'mac' grant. + * + * @param AccessToken $token + * @return array + * @codeCoverageIgnore + * + * @todo This is currently untested and provided only as an example. If you + * complete the implementation, please create a pull request for + * https://github.com/thephpleague/oauth2-client + */ + protected function getAuthorizationHeaders($token) + { + $ts = time(); + $id = $this->getTokenId($token); + $nonce = $this->getRandomState(16); + $mac = $this->getMacSignature($id, $ts, $nonce); + + $parts = []; + foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) { + $parts[] = sprintf('%s="%s"', $key, $value); + } + + return ['Authorization' => 'MAC ' . implode(', ', $parts)]; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php b/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php new file mode 100644 index 0000000000..f81b511f9e --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/ProviderRedirectTrait.php @@ -0,0 +1,122 @@ +redirectLimit) { + $attempts++; + $response = $this->getHttpClient()->send($request, [ + 'allow_redirects' => false + ]); + + if ($this->isRedirect($response)) { + $redirectUrl = new Uri($response->getHeader('Location')[0]); + $request = $request->withUri($redirectUrl); + } else { + break; + } + } + + return $response; + } + + /** + * Returns the HTTP client instance. + * + * @return GuzzleHttp\ClientInterface + */ + abstract public function getHttpClient(); + + /** + * Retrieves current redirect limit. + * + * @return integer + */ + public function getRedirectLimit() + { + return $this->redirectLimit; + } + + /** + * Determines if a given response is a redirect. + * + * @param ResponseInterface $response + * + * @return boolean + */ + protected function isRedirect(ResponseInterface $response) + { + $statusCode = $response->getStatusCode(); + + return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location'); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + try { + $response = $this->followRequestRedirects($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + return $response; + } + + /** + * Updates the redirect limit. + * + * @param integer $limit + * @return League\OAuth2\Client\Provider\AbstractProvider + * @throws InvalidArgumentException + */ + public function setRedirectLimit($limit) + { + if (!is_int($limit)) { + throw new InvalidArgumentException('redirectLimit must be an integer.'); + } + + if ($limit < 1) { + throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.'); + } + + $this->redirectLimit = $limit; + + return $this; + } +} diff --git a/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php b/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php new file mode 100644 index 0000000000..ebccdffc6e --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/QueryBuilderTrait.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +/** + * Provides a standard way to generate query strings. + */ +trait QueryBuilderTrait +{ + /** + * Build a query string from an array. + * + * @param array $params + * + * @return string + */ + protected function buildQueryString(array $params) + { + return http_build_query($params, null, '&', \PHP_QUERY_RFC3986); + } +} diff --git a/vendor/league/oauth2-client/src/Tool/RequestFactory.php b/vendor/league/oauth2-client/src/Tool/RequestFactory.php new file mode 100644 index 0000000000..1af434297f --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/RequestFactory.php @@ -0,0 +1,87 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use GuzzleHttp\Psr7\Request; + +/** + * Used to produce PSR-7 Request instances. + * + * @link https://github.com/guzzle/guzzle/pull/1101 + */ +class RequestFactory +{ + /** + * Creates a PSR-7 Request instance. + * + * @param null|string $method HTTP method for the request. + * @param null|string $uri URI for the request. + * @param array $headers Headers for the message. + * @param string|resource|StreamInterface $body Message body. + * @param string $version HTTP protocol version. + * + * @return Request + */ + public function getRequest( + $method, + $uri, + array $headers = [], + $body = null, + $version = '1.1' + ) { + return new Request($method, $uri, $headers, $body, $version); + } + + /** + * Parses simplified options. + * + * @param array $options Simplified options. + * + * @return array Extended options for use with getRequest. + */ + protected function parseOptions(array $options) + { + // Should match default values for getRequest + $defaults = [ + 'headers' => [], + 'body' => null, + 'version' => '1.1', + ]; + + return array_merge($defaults, $options); + } + + /** + * Creates a request using a simplified array of options. + * + * @param null|string $method + * @param null|string $uri + * @param array $options + * + * @return Request + */ + public function getRequestWithOptions($method, $uri, array $options = []) + { + $options = $this->parseOptions($options); + + return $this->getRequest( + $method, + $uri, + $options['headers'], + $options['body'], + $options['version'] + ); + } +} diff --git a/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php b/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php new file mode 100644 index 0000000000..47da977177 --- /dev/null +++ b/vendor/league/oauth2-client/src/Tool/RequiredParameterTrait.php @@ -0,0 +1,56 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Tool; + +use BadMethodCallException; + +/** + * Provides functionality to check for required parameters. + */ +trait RequiredParameterTrait +{ + /** + * Checks for a required parameter in a hash. + * + * @throws BadMethodCallException + * @param string $name + * @param array $params + * @return void + */ + private function checkRequiredParameter($name, array $params) + { + if (!isset($params[$name])) { + throw new BadMethodCallException(sprintf( + 'Required parameter not passed: "%s"', + $name + )); + } + } + + /** + * Checks for multiple required parameters in a hash. + * + * @throws InvalidArgumentException + * @param array $names + * @param array $params + * @return void + */ + private function checkRequiredParameters(array $names, array $params) + { + foreach ($names as $name) { + $this->checkRequiredParameter($name, $params); + } + } +} diff --git a/vendor/league/oauth2-facebook/LICENSE b/vendor/league/oauth2-facebook/LICENSE new file mode 100755 index 0000000000..dd7005d9e5 --- /dev/null +++ b/vendor/league/oauth2-facebook/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sammy Kaye Powers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/oauth2-facebook/composer.json b/vendor/league/oauth2-facebook/composer.json new file mode 100755 index 0000000000..a977eecfec --- /dev/null +++ b/vendor/league/oauth2-facebook/composer.json @@ -0,0 +1,39 @@ +{ + "name": "league/oauth2-facebook", + "description": "Facebook OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Sammy Kaye Powers", + "email": "me@sammyk.me", + "homepage": "http://www.sammyk.me" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "authentication", + "facebook" + ], + "require": { + "php": "^5.6 || ^7.0", + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "mockery/mockery": "~0.9", + "squizlabs/php_codesniffer": "~2.0" + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "tests/src/" + } + } +} diff --git a/vendor/league/oauth2-facebook/src/Grant/FbExchangeToken.php b/vendor/league/oauth2-facebook/src/Grant/FbExchangeToken.php new file mode 100644 index 0000000000..aa06490882 --- /dev/null +++ b/vendor/league/oauth2-facebook/src/Grant/FbExchangeToken.php @@ -0,0 +1,23 @@ +graphApiVersion = $options['graphApiVersion']; + + if (!empty($options['enableBetaTier']) && $options['enableBetaTier'] === true) { + $this->enableBetaMode = true; + } + } + + public function getBaseAuthorizationUrl() + { + return $this->getBaseFacebookUrl().$this->graphApiVersion.'/dialog/oauth'; + } + + public function getBaseAccessTokenUrl(array $params) + { + return $this->getBaseGraphUrl().$this->graphApiVersion.'/oauth/access_token'; + } + + public function getDefaultScopes() + { + return ['public_profile', 'email']; + } + + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + $fields = [ + 'id', 'name', 'first_name', 'last_name', + 'email', 'hometown', 'picture.type(large){url,is_silhouette}', + 'cover{source}', 'gender', 'locale', 'link', 'timezone', 'age_range' + ]; + + // backwards compatibility less than 2.8 + if (version_compare(substr($this->graphApiVersion, 1), '2.8') < 0) { + $fields[] = 'bio'; + } + + $appSecretProof = AppSecretProof::create($this->clientSecret, $token->getToken()); + + return $this->getBaseGraphUrl().$this->graphApiVersion.'/me?fields='.implode(',', $fields) + .'&access_token='.$token.'&appsecret_proof='.$appSecretProof; + } + + public function getAccessToken($grant = 'authorization_code', array $params = []) + { + if (isset($params['refresh_token'])) { + throw new FacebookProviderException('Facebook does not support token refreshing.'); + } + + return parent::getAccessToken($grant, $params); + } + + /** + * Exchanges a short-lived access token with a long-lived access-token. + * + * @param string $accessToken + * + * @return \League\OAuth2\Client\Token\AccessToken + * + * @throws FacebookProviderException + */ + public function getLongLivedAccessToken($accessToken) + { + $params = [ + 'fb_exchange_token' => (string) $accessToken, + ]; + + return $this->getAccessToken('fb_exchange_token', $params); + } + + protected function createResourceOwner(array $response, AccessToken $token) + { + return new FacebookUser($response); + } + + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data['error'])) { + $message = $data['error']['type'].': '.$data['error']['message']; + throw new IdentityProviderException($message, $data['error']['code'], $data); + } + } + + /** + * @inheritdoc + */ + protected function getContentType(ResponseInterface $response) + { + $type = parent::getContentType($response); + + // Fix for Facebook's pseudo-JSONP support + if (strpos($type, 'javascript') !== false) { + return 'application/json'; + } + + // Fix for Facebook's pseudo-urlencoded support + if (strpos($type, 'plain') !== false) { + return 'application/x-www-form-urlencoded'; + } + + return $type; + } + + /** + * Get the base Facebook URL. + * + * @return string + */ + private function getBaseFacebookUrl() + { + return $this->enableBetaMode ? static::BASE_FACEBOOK_URL_BETA : static::BASE_FACEBOOK_URL; + } + + /** + * Get the base Graph API URL. + * + * @return string + */ + private function getBaseGraphUrl() + { + return $this->enableBetaMode ? static::BASE_GRAPH_URL_BETA : static::BASE_GRAPH_URL; + } +} diff --git a/vendor/league/oauth2-facebook/src/Provider/FacebookUser.php b/vendor/league/oauth2-facebook/src/Provider/FacebookUser.php new file mode 100755 index 0000000000..57a4b47ee1 --- /dev/null +++ b/vendor/league/oauth2-facebook/src/Provider/FacebookUser.php @@ -0,0 +1,221 @@ +data = $response; + + if (!empty($response['picture']['data']['url'])) { + $this->data['picture_url'] = $response['picture']['data']['url']; + } + + if (isset($response['picture']['data']['is_silhouette'])) { + $this->data['is_silhouette'] = $response['picture']['data']['is_silhouette']; + } + + if (!empty($response['cover']['source'])) { + $this->data['cover_photo_url'] = $response['cover']['source']; + } + } + + /** + * Returns the ID for the user as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the name for the user as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the first name for the user as a string if present. + * + * @return string|null + */ + public function getFirstName() + { + return $this->getField('first_name'); + } + + /** + * Returns the last name for the user as a string if present. + * + * @return string|null + */ + public function getLastName() + { + return $this->getField('last_name'); + } + + /** + * Returns the email for the user as a string if present. + * + * @return string|null + */ + public function getEmail() + { + return $this->getField('email'); + } + + /** + * Returns the current location of the user as an array. + * + * @return array|null + */ + public function getHometown() + { + return $this->getField('hometown'); + } + + /** + * Returns the "about me" bio for the user as a string if present. + * + * @return string|null + * @deprecated The bio field was removed in Graph v2.8 + */ + public function getBio() + { + return $this->getField('bio'); + } + + /** + * Returns if user has not defined a specific avatar + * + * @return boolean + */ + + public function isDefaultPicture() + { + return $this->getField('is_silhouette'); + } + + /** + * Returns the profile picture of the user as a string if present. + * + * @return string|null + */ + public function getPictureUrl() + { + return $this->getField('picture_url'); + } + + /** + * Returns the cover photo URL of the user as a string if present. + * + * @return string|null + */ + public function getCoverPhotoUrl() + { + return $this->getField('cover_photo_url'); + } + + /** + * Returns the gender for the user as a string if present. + * + * @return string|null + */ + public function getGender() + { + return $this->getField('gender'); + } + + /** + * Returns the locale of the user as a string if available. + * + * @return string|null + */ + public function getLocale() + { + return $this->getField('locale'); + } + + /** + * Returns the Facebook URL for the user as a string if available. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the current timezone offset from UTC (from -24 to 24) + * + * @return float|null + */ + public function getTimezone() + { + return $this->getField('timezone'); + } + + /** + * Returns the lower bound of the user's age range + * + * @return integer|null + */ + public function getMinAge() + { + if (isset($this->data['age_range']['min'])) { + return $this->data['age_range']['min']; + } + return null; + } + + /** + * Returns the upper bound of the user's age range + * + * @return integer|null + */ + public function getMaxAge() + { + if (isset($this->data['age_range']['max'])) { + return $this->data['age_range']['max']; + } + return null; + } + + /** + * Returns all the data obtained about the user. + * + * @return array + */ + public function toArray() + { + return $this->data; + } + + /** + * Returns a field from the Graph node data. + * + * @param string $key + * + * @return mixed|null + */ + private function getField($key) + { + return isset($this->data[$key]) ? $this->data[$key] : null; + } +} diff --git a/vendor/league/oauth2-github/.scrutinizer.yml b/vendor/league/oauth2-github/.scrutinizer.yml new file mode 100644 index 0000000000..d5851073e2 --- /dev/null +++ b/vendor/league/oauth2-github/.scrutinizer.yml @@ -0,0 +1,35 @@ +filter: + excluded_paths: [test/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 600 + runs: 3 + php_analyzer: true + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, test] + php_cpd: + enabled: true + excluded_dirs: [vendor, test] diff --git a/vendor/league/oauth2-github/.travis.yml b/vendor/league/oauth2-github/.travis.yml new file mode 100644 index 0000000000..d99d282141 --- /dev/null +++ b/vendor/league/oauth2-github/.travis.yml @@ -0,0 +1,27 @@ +language: php + +sudo: false + +php: + - 5.6 + - 7.0 + - 7.1 + - hhvm + +matrix: + include: + - php: 5.6 + env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"' + +before_script: + - travis_retry composer self-update + - travis_retry composer install --no-interaction --prefer-source --dev + - travis_retry phpenv rehash + +script: + - ./vendor/bin/phpcs --standard=psr2 src/ + - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/vendor/league/oauth2-github/CHANGELOG.md b/vendor/league/oauth2-github/CHANGELOG.md new file mode 100644 index 0000000000..68ae784fda --- /dev/null +++ b/vendor/league/oauth2-github/CHANGELOG.md @@ -0,0 +1,92 @@ +# Changelog +All Notable changes to `oauth2-github` will be documented in this file + +## 2.0.0 - 2017-01-25 + +### Added +- PHP 7.1 Support + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Removed +- PHP 5.5 Support + +### Security +- Nothing + +## 1.0.0 - 2017-01-25 + +Bump for base package parity + +## 0.2.2 - 2016-11-21 + +### Added +- Update base package version from 1.0 to 1.4 +- Update GithubResourceOwner to utilize ArrayAccessorTrait from base package + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Removed +- Nothing + +### Security +- Nothing + +## 0.2.1 - 2016-04-13 + +### Added +- Support OAuth exceptions from Github with non-standard status codes (https://developer.github.com/v3/oauth/#common-errors-for-the-access-token-request) + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Removed +- Nothing + +### Security +- Nothing + +## 0.2.0 - 2015-08-20 + +### Added +- Upgrade to support version 1.0 release of core client + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Removed +- Nothing + +### Security +- Nothing + +## 0.1.0 - 2015-04-13 + +### Added +- Initial release! + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Removed +- Nothing + +### Security +- Nothing diff --git a/vendor/league/oauth2-github/CONTRIBUTING.md b/vendor/league/oauth2-github/CONTRIBUTING.md new file mode 100644 index 0000000000..06745a0081 --- /dev/null +++ b/vendor/league/oauth2-github/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-github). + + +## Pull Requests + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. + +- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. + + +## Running Tests + +``` bash +$ ./vendor/bin/phpunit +``` + + +## Running PHP Code Sniffer + +``` bash +$ ./vendor/bin/phpcs src --standard=psr2 -sp +``` + +**Happy coding**! diff --git a/vendor/league/oauth2-github/LICENSE b/vendor/league/oauth2-github/LICENSE new file mode 100644 index 0000000000..51455e2dd8 --- /dev/null +++ b/vendor/league/oauth2-github/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Steven Maguire + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/oauth2-github/README.md b/vendor/league/oauth2-github/README.md new file mode 100644 index 0000000000..2fc3a59739 --- /dev/null +++ b/vendor/league/oauth2-github/README.md @@ -0,0 +1,129 @@ +# Github Provider for OAuth 2.0 Client +[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-github.svg?style=flat-square)](https://github.com/thephpleague/oauth2-github/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-github/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/oauth2-github) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth2-github.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-github/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-github.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-github) +[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-github.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-github) + +This package provides Github OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +## Installation + +To install, use composer: + +``` +composer require league/oauth2-github +``` + +## Usage + +Usage is the same as The League's OAuth client, using `\League\OAuth2\Client\Provider\Github` as the provider. + +### Authorization Code Flow + +```php +$provider = new League\OAuth2\Client\Provider\Github([ + 'clientId' => '{github-client-id}', + 'clientSecret' => '{github-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', +]); + +if (!isset($_GET['code'])) { + + // If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: '.$authUrl); + exit; + +// Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + // Optional: Now you have a token you can look up a users profile data + try { + + // We got an access token, let's now get the user's details + $user = $provider->getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!', $user->getNickname()); + + } catch (Exception $e) { + + // Failed to get user details + exit('Oh dear...'); + } + + // Use this to interact with an API on the users behalf + echo $token->getToken(); +} +``` + +### Managing Scopes + +When creating your Github authorization URL, you can specify the state and scopes your application may authorize. + +```php +$options = [ + 'state' => 'OPTIONAL_CUSTOM_CONFIGURED_STATE', + 'scope' => ['user','user:email','repo'] // array or string +]; + +$authorizationUrl = $provider->getAuthorizationUrl($options); +``` +If neither are defined, the provider will utilize internal defaults. + +At the time of authoring this documentation, the [following scopes are available](https://developer.github.com/v3/oauth/#scopes). + +- user +- user:email +- user:follow +- public_repo +- repo +- repo_deployment +- repo:status +- delete_repo +- notifications +- gist +- read:repo_hook +- write:repo_hook +- admin:repo_hook +- admin:org_hook +- read:org +- write:org +- admin:org +- read:public_key +- write:public_key +- admin:public_key + +## Testing + +``` bash +$ ./vendor/bin/phpunit +``` + +## Contributing + +Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-github/blob/master/CONTRIBUTING.md) for details. + + +## Credits + +- [Steven Maguire](https://github.com/stevenmaguire) +- [All Contributors](https://github.com/thephpleague/oauth2-github/contributors) + + +## License + +The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-github/blob/master/LICENSE) for more information. diff --git a/vendor/league/oauth2-github/composer.json b/vendor/league/oauth2-github/composer.json new file mode 100644 index 0000000000..f88f66ba0b --- /dev/null +++ b/vendor/league/oauth2-github/composer.json @@ -0,0 +1,43 @@ +{ + "name": "league/oauth2-github", + "description": "Github OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "authorisation", + "github" + ], + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "mockery/mockery": "~0.9", + "squizlabs/php_codesniffer": "~2.0" + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/league/oauth2-github/phpunit.xml b/vendor/league/oauth2-github/phpunit.xml new file mode 100644 index 0000000000..1819eb04aa --- /dev/null +++ b/vendor/league/oauth2-github/phpunit.xml @@ -0,0 +1,37 @@ + + + + + + + + + ./test/ + + + + + ./ + + ./vendor + ./test + + + + diff --git a/vendor/league/oauth2-github/src/Provider/Exception/GithubIdentityProviderException.php b/vendor/league/oauth2-github/src/Provider/Exception/GithubIdentityProviderException.php new file mode 100644 index 0000000000..bb30ace169 --- /dev/null +++ b/vendor/league/oauth2-github/src/Provider/Exception/GithubIdentityProviderException.php @@ -0,0 +1,53 @@ +getReasonPhrase() + ); + } + + /** + * Creates oauth exception from response. + * + * @param ResponseInterface $response + * @param string $data Parsed response data + * + * @return IdentityProviderException + */ + public static function oauthException(ResponseInterface $response, $data) + { + return static::fromResponse( + $response, + isset($data['error']) ? $data['error'] : $response->getReasonPhrase() + ); + } + + /** + * Creates identity exception from response. + * + * @param ResponseInterface $response + * @param string $message + * + * @return IdentityProviderException + */ + protected static function fromResponse(ResponseInterface $response, $message = null) + { + return new static($message, $response->getStatusCode(), (string) $response->getBody()); + } +} diff --git a/vendor/league/oauth2-github/src/Provider/Github.php b/vendor/league/oauth2-github/src/Provider/Github.php new file mode 100644 index 0000000000..9946adf7aa --- /dev/null +++ b/vendor/league/oauth2-github/src/Provider/Github.php @@ -0,0 +1,110 @@ +domain.'/login/oauth/authorize'; + } + + /** + * Get access token url to retrieve token + * + * @param array $params + * + * @return string + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->domain.'/login/oauth/access_token'; + } + + /** + * Get provider url to fetch user details + * + * @param AccessToken $token + * + * @return string + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + if ($this->domain === 'https://github.com') { + return $this->apiDomain.'/user'; + } + return $this->domain.'/api/v3/user'; + } + + /** + * Get the default scopes used by this provider. + * + * This should not be a complete list of all scopes, but the minimum + * required for the provider user interface! + * + * @return array + */ + protected function getDefaultScopes() + { + return []; + } + + /** + * Check a provider response for errors. + * + * @link https://developer.github.com/v3/#client-errors + * @link https://developer.github.com/v3/oauth/#common-errors-for-the-access-token-request + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param string $data Parsed response data + * @return void + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if ($response->getStatusCode() >= 400) { + throw GithubIdentityProviderException::clientException($response, $data); + } elseif (isset($data['error'])) { + throw GithubIdentityProviderException::oauthException($response, $data); + } + } + + /** + * Generate a user object from a successful user details request. + * + * @param array $response + * @param AccessToken $token + * @return League\OAuth2\Client\Provider\ResourceOwnerInterface + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + $user = new GithubResourceOwner($response); + + return $user->setDomain($this->domain); + } +} diff --git a/vendor/league/oauth2-github/src/Provider/GithubResourceOwner.php b/vendor/league/oauth2-github/src/Provider/GithubResourceOwner.php new file mode 100644 index 0000000000..712e496a23 --- /dev/null +++ b/vendor/league/oauth2-github/src/Provider/GithubResourceOwner.php @@ -0,0 +1,108 @@ +response = $response; + } + + /** + * Get resource owner id + * + * @return string|null + */ + public function getId() + { + return $this->getValueByKey($this->response, 'id'); + } + + /** + * Get resource owner email + * + * @return string|null + */ + public function getEmail() + { + return $this->getValueByKey($this->response, 'email'); + } + + /** + * Get resource owner name + * + * @return string|null + */ + public function getName() + { + return $this->getValueByKey($this->response, 'name'); + } + + /** + * Get resource owner nickname + * + * @return string|null + */ + public function getNickname() + { + return $this->getValueByKey($this->response, 'login'); + } + + /** + * Get resource owner url + * + * @return string|null + */ + public function getUrl() + { + $urlParts = array_filter([$this->domain, $this->getNickname()]); + + return count($urlParts) ? implode('/', $urlParts) : null; + } + + /** + * Set resource owner domain + * + * @param string $domain + * + * @return ResourceOwner + */ + public function setDomain($domain) + { + $this->domain = $domain; + + return $this; + } + + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/vendor/league/oauth2-github/test/src/Provider/GithubResourceOwnerTest.php b/vendor/league/oauth2-github/test/src/Provider/GithubResourceOwnerTest.php new file mode 100644 index 0000000000..79568b4700 --- /dev/null +++ b/vendor/league/oauth2-github/test/src/Provider/GithubResourceOwnerTest.php @@ -0,0 +1,36 @@ +getUrl(); + + $this->assertNull($url); + } + + public function testUrlIsDomainWithoutNickname() + { + $domain = uniqid(); + $user = new \League\OAuth2\Client\Provider\GithubResourceOwner; + $user->setDomain($domain); + + $url = $user->getUrl(); + + $this->assertEquals($domain, $url); + } + + public function testUrlIsNicknameWithoutDomain() + { + $nickname = uniqid(); + $user = new \League\OAuth2\Client\Provider\GithubResourceOwner(['login' => $nickname]); + + $url = $user->getUrl(); + + $this->assertEquals($nickname, $url); + } +} diff --git a/vendor/league/oauth2-github/test/src/Provider/GithubTest.php b/vendor/league/oauth2-github/test/src/Provider/GithubTest.php new file mode 100644 index 0000000000..fd01b6866a --- /dev/null +++ b/vendor/league/oauth2-github/test/src/Provider/GithubTest.php @@ -0,0 +1,215 @@ +provider = new \League\OAuth2\Client\Provider\Github([ + 'clientId' => 'mock_client_id', + 'clientSecret' => 'mock_secret', + 'redirectUri' => 'none', + ]); + } + + public function tearDown() + { + m::close(); + parent::tearDown(); + } + + public function testAuthorizationUrl() + { + $url = $this->provider->getAuthorizationUrl(); + $uri = parse_url($url); + parse_str($uri['query'], $query); + + $this->assertArrayHasKey('client_id', $query); + $this->assertArrayHasKey('redirect_uri', $query); + $this->assertArrayHasKey('state', $query); + $this->assertArrayHasKey('scope', $query); + $this->assertArrayHasKey('response_type', $query); + $this->assertArrayHasKey('approval_prompt', $query); + $this->assertNotNull($this->provider->getState()); + } + + + public function testScopes() + { + $options = ['scope' => [uniqid(),uniqid()]]; + + $url = $this->provider->getAuthorizationUrl($options); + + $this->assertContains(urlencode(implode(',', $options['scope'])), $url); + } + + public function testGetAuthorizationUrl() + { + $url = $this->provider->getAuthorizationUrl(); + $uri = parse_url($url); + + $this->assertEquals('/login/oauth/authorize', $uri['path']); + } + + public function testGetBaseAccessTokenUrl() + { + $params = []; + + $url = $this->provider->getBaseAccessTokenUrl($params); + $uri = parse_url($url); + + $this->assertEquals('/login/oauth/access_token', $uri['path']); + } + + public function testGetAccessToken() + { + $response = m::mock('Psr\Http\Message\ResponseInterface'); + $response->shouldReceive('getBody')->andReturn('{"access_token":"mock_access_token", "scope":"repo,gist", "token_type":"bearer"}'); + $response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $response->shouldReceive('getStatusCode')->andReturn(200); + + $client = m::mock('GuzzleHttp\ClientInterface'); + $client->shouldReceive('send')->times(1)->andReturn($response); + $this->provider->setHttpClient($client); + + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + + $this->assertEquals('mock_access_token', $token->getToken()); + $this->assertNull($token->getExpires()); + $this->assertNull($token->getRefreshToken()); + $this->assertNull($token->getResourceOwnerId()); + } + + public function testGithubEnterpriseDomainUrls() + { + $this->provider->domain = 'https://github.company.com'; + + + $response = m::mock('Psr\Http\Message\ResponseInterface'); + $response->shouldReceive('getBody')->times(1)->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}'); + $response->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']); + $response->shouldReceive('getStatusCode')->andReturn(200); + + $client = m::mock('GuzzleHttp\ClientInterface'); + $client->shouldReceive('send')->times(1)->andReturn($response); + $this->provider->setHttpClient($client); + + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + + $this->assertEquals($this->provider->domain.'/login/oauth/authorize', $this->provider->getBaseAuthorizationUrl()); + $this->assertEquals($this->provider->domain.'/login/oauth/access_token', $this->provider->getBaseAccessTokenUrl([])); + $this->assertEquals($this->provider->domain.'/api/v3/user', $this->provider->getResourceOwnerDetailsUrl($token)); + //$this->assertEquals($this->provider->domain.'/api/v3/user/emails', $this->provider->urlUserEmails($token)); + } + + public function testUserData() + { + $userId = rand(1000,9999); + $name = uniqid(); + $nickname = uniqid(); + $email = uniqid(); + + $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $postResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}'); + $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']); + $postResponse->shouldReceive('getStatusCode')->andReturn(200); + + $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $userResponse->shouldReceive('getBody')->andReturn('{"id": '.$userId.', "login": "'.$nickname.'", "name": "'.$name.'", "email": "'.$email.'"}'); + $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $userResponse->shouldReceive('getStatusCode')->andReturn(200); + + $client = m::mock('GuzzleHttp\ClientInterface'); + $client->shouldReceive('send') + ->times(2) + ->andReturn($postResponse, $userResponse); + $this->provider->setHttpClient($client); + + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + $user = $this->provider->getResourceOwner($token); + + $this->assertEquals($userId, $user->getId()); + $this->assertEquals($userId, $user->toArray()['id']); + $this->assertEquals($name, $user->getName()); + $this->assertEquals($name, $user->toArray()['name']); + $this->assertEquals($nickname, $user->getNickname()); + $this->assertEquals($nickname, $user->toArray()['login']); + $this->assertEquals($email, $user->getEmail()); + $this->assertEquals($email, $user->toArray()['email']); + $this->assertContains($nickname, $user->getUrl()); + } + + public function testUserEmails() + { + /* + $userId = rand(1000,9999); + $name = uniqid(); + $nickname = uniqid(); + $email = uniqid(); + + $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $postResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}'); + $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']); + + $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $userResponse->shouldReceive('getBody')->andReturn('[{"email":"mock_email_1","primary":false,"verified":true},{"email":"mock_email_2","primary":false,"verified":true},{"email":"mock_email_3","primary":true,"verified":true}]'); + $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + + $client = m::mock('GuzzleHttp\ClientInterface'); + $client->shouldReceive('send') + ->times(2) + ->andReturn($postResponse, $userResponse); + $this->provider->setHttpClient($client); + + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + $emails = $this->provider->getUserEmails($token); + + $this->assertEquals($userId, $user->getUserId()); + $this->assertEquals($name, $user->getName()); + $this->assertEquals($nickname, $user->getNickname()); + $this->assertEquals($email, $user->getEmail()); + $this->assertContains($nickname, $user->getUrl()); + */ + } + + /** + * @expectedException League\OAuth2\Client\Provider\Exception\IdentityProviderException + **/ + public function testExceptionThrownWhenErrorObjectReceived() + { + $status = rand(400,600); + $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $postResponse->shouldReceive('getBody')->andReturn('{"message": "Validation Failed","errors": [{"resource": "Issue","field": "title","code": "missing_field"}]}'); + $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $postResponse->shouldReceive('getStatusCode')->andReturn($status); + + $client = m::mock('GuzzleHttp\ClientInterface'); + $client->shouldReceive('send') + ->times(1) + ->andReturn($postResponse); + $this->provider->setHttpClient($client); + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + } + + /** + * @expectedException League\OAuth2\Client\Provider\Exception\IdentityProviderException + **/ + public function testExceptionThrownWhenOAuthErrorReceived() + { + $status = 200; + $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $postResponse->shouldReceive('getBody')->andReturn('{"error": "bad_verification_code","error_description": "The code passed is incorrect or expired.","error_uri": "https://developer.github.com/v3/oauth/#bad-verification-code"}'); + $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $postResponse->shouldReceive('getStatusCode')->andReturn($status); + + $client = m::mock('GuzzleHttp\ClientInterface'); + $client->shouldReceive('send') + ->times(1) + ->andReturn($postResponse); + $this->provider->setHttpClient($client); + $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + } +} diff --git a/vendor/league/oauth2-google/CONTRIBUTING.md b/vendor/league/oauth2-google/CONTRIBUTING.md new file mode 100644 index 0000000000..9dd0b4ddcc --- /dev/null +++ b/vendor/league/oauth2-google/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-google). + + +## Pull Requests + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. + +- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. + + +## Running Tests + +``` bash +$ ./vendor/bin/phpunit +``` + + +## Running PHP Code Sniffer + +``` bash +$ ./vendor/bin/phpcs src --standard=psr2 -sp +``` + +**Happy coding**! diff --git a/vendor/league/oauth2-google/LICENSE b/vendor/league/oauth2-google/LICENSE new file mode 100644 index 0000000000..6d451561ed --- /dev/null +++ b/vendor/league/oauth2-google/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Woody Gilk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/league/oauth2-google/README.md b/vendor/league/oauth2-google/README.md new file mode 100644 index 0000000000..d7056e0eb0 --- /dev/null +++ b/vendor/league/oauth2-google/README.md @@ -0,0 +1,190 @@ +# Google Provider for OAuth 2.0 Client + +[![Join the chat](https://img.shields.io/badge/gitter-join-1DCE73.svg)](https://gitter.im/thephpleague/oauth2-google) +[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-google.svg)](https://travis-ci.org/thephpleague/oauth2-google) +[![Code Coverage](https://img.shields.io/coveralls/thephpleague/oauth2-google.svg)](https://coveralls.io/r/thephpleague/oauth2-google) +[![Code Quality](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-google.svg)](https://scrutinizer-ci.com/g/thephpleague/oauth2-google/) +[![License](https://img.shields.io/packagist/l/league/oauth2-google.svg)](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) +[![Latest Stable Version](https://img.shields.io/packagist/v/league/oauth2-google.svg)](https://packagist.org/packages/league/oauth2-google) + +This package provides Google OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +This package is compliant with [PSR-1][], [PSR-2][] and [PSR-4][]. If you notice compliance oversights, please send +a patch via pull request. + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md + +## Requirements + +The following versions of PHP are supported. + +* PHP 5.6 +* PHP 7.0 +* PHP 7.1 +* HHVM + +[Google Sign In](https://developers.google.com/identity/sign-in/web/sign-in) will also need to be set up, which will provide you with the `{google-app-id}` and `{google-app-secret}` required (see [Usage](#usage) below). + +If you're using the default [scopes](#scopes) then you'll also need to enable the [Google+ API](https://developers.google.com/+/web/api/rest/) for your project. + +## Installation + +To install, use composer: + +``` +composer require league/oauth2-google +``` + +## Usage + +### Authorization Code Flow + +```php +$provider = new League\OAuth2\Client\Provider\Google([ + 'clientId' => '{google-app-id}', + 'clientSecret' => '{google-app-secret}', + 'redirectUri' => 'https://example.com/callback-url', + 'hostedDomain' => 'example.com', +]); + +if (!empty($_GET['error'])) { + + // Got an error, probably user denied access + exit('Got error: ' . htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8')); + +} elseif (empty($_GET['code'])) { + + // If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + // State is invalid, possible CSRF attack in progress + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + // Optional: Now you have a token you can look up a users profile data + try { + + // We got an access token, let's now get the owner details + $ownerDetails = $provider->getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!', $ownerDetails->getFirstName()); + + } catch (Exception $e) { + + // Failed to get user details + exit('Something went wrong: ' . $e->getMessage()); + + } + + // Use this to interact with an API on the users behalf + echo $token->getToken(); + + // Use this to get a new access token if the old one expires + echo $token->getRefreshToken(); + + // Number of seconds until the access token will expire, and need refreshing + echo $token->getExpires(); +} +``` + +### Refreshing a Token + +Refresh tokens are only provided to applications which request offline access. You can specify offline access by setting the `accessType` option in your provider: + +```php +$provider = new League\OAuth2\Client\Provider\Google([ + 'clientId' => '{google-app-id}', + 'clientSecret' => '{google-app-secret}', + 'redirectUri' => 'https://example.com/callback-url', + 'accessType' => 'offline', +]); +``` + +It is important to note that the refresh token is only returned on the first request after this it will be `null`. You should securely store the refresh token when it is returned: + +```php +$token = $provider->getAccessToken('authorization_code', [ + 'code' => $code +]); + +// persist the token in a database +$refreshToken = $token->getRefreshToken(); +``` + +If you ever need to get a new refresh token you can request one by forcing the approval prompt: + +```php +$authUrl = $provider->getAuthorizationUrl(['approval_prompt' => 'force']); +``` + +Now you have everything you need to refresh an access token using a refresh token: + +```php +$provider = new League\OAuth2\Client\Provider\Google([ + 'clientId' => '{google-app-id}', + 'clientSecret' => '{google-app-secret}', + 'redirectUri' => 'https://example.com/callback-url', +]); + +$grant = new League\OAuth2\Client\Grant\RefreshToken(); +$token = $provider->getAccessToken($grant, ['refresh_token' => $refreshToken]); +``` +## Resource Owner Attributes + +By default the Google plus API is used to load profile information. If you want to use the OpenIDConnect +user info endpoint to load profile information then add `useOidcMode => true` to your configuration. + +The two endpoints provide attributes with different names and structures. The `GoogleUser` class hides +these differences for the most common attributes. + +## Scopes + +If needed, you can include an array of scopes when getting the authorization url. Example: + +``` +$authorizationUrl = $provider->getAuthorizationUrl([ + 'scope' => [ + 'https://www.googleapis.com/auth/drive', + ] +]); +header('Location: ' . $authorizationUrl); +exit; +``` + +Note that the default scopes include `email` and `profile`, which require that the [Google+ API](https://developers.google.com/+/web/api/rest/) is enabled for your project. + +## Testing + +``` bash +$ ./vendor/bin/phpunit +``` + +## Contributing + +Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-google/blob/master/CONTRIBUTING.md) for details. + + +## Credits + +- [Woody Gilk](https://github.com/shadowhand) +- [All Contributors](https://github.com/thephpleague/oauth2-google/contributors) + + +## License + +The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) for more information. diff --git a/vendor/league/oauth2-google/composer.json b/vendor/league/oauth2-google/composer.json new file mode 100644 index 0000000000..eaebe548df --- /dev/null +++ b/vendor/league/oauth2-google/composer.json @@ -0,0 +1,44 @@ +{ + "name": "league/oauth2-google", + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "authentication", + "google" + ], + "minimum-stability": "stable", + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony": "^0.14.6", + "phpunit/phpunit": "^5.7", + "satooshi/php-coveralls": "^2.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "tests/src/" + } + }, + "scripts": { + "test": "phpunit", + "check": "phpcs src --standard=psr2 -sp" + } +} diff --git a/vendor/league/oauth2-google/examples/index.php b/vendor/league/oauth2-google/examples/index.php new file mode 100644 index 0000000000..9138437770 --- /dev/null +++ b/vendor/league/oauth2-google/examples/index.php @@ -0,0 +1,35 @@ +getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + // State is invalid, possible CSRF attack in progress + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + $_SESSION['token'] = serialize($token); + + // Optional: Now you have a token you can look up a users profile data + header('Location: /user.php'); +} diff --git a/vendor/league/oauth2-google/examples/provider.php b/vendor/league/oauth2-google/examples/provider.php new file mode 100644 index 0000000000..4001f68570 --- /dev/null +++ b/vendor/league/oauth2-google/examples/provider.php @@ -0,0 +1,24 @@ +getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!
    ', $userDetails->getFirstname()); + +} catch (Exception $e) { + + // Failed to get user details + exit('Something went wrong: ' . $e->getMessage()); + +} + +// Use this to interact with an API on the users behalf +echo $token->getToken()."
    "; + +// Use this to get a new access token if the old one expires +echo $token->getRefreshToken()."
    "; + +// Number of seconds until the access token will expire, and need refreshing +echo $token->getExpires()."
    "; diff --git a/vendor/league/oauth2-google/phpunit.xml.dist b/vendor/league/oauth2-google/phpunit.xml.dist new file mode 100644 index 0000000000..7f37586aab --- /dev/null +++ b/vendor/league/oauth2-google/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + ./test + + + + + src/ + + + + + + + + diff --git a/vendor/league/oauth2-google/src/Exception/HostedDomainException.php b/vendor/league/oauth2-google/src/Exception/HostedDomainException.php new file mode 100644 index 0000000000..1adf9acb3e --- /dev/null +++ b/vendor/league/oauth2-google/src/Exception/HostedDomainException.php @@ -0,0 +1,15 @@ +useOidcMode) { + // OIDC endpoints can be found https://accounts.google.com/.well-known/openid-configuration + return 'https://www.googleapis.com/oauth2/v3/userinfo'; + } + // fields that are required based on other configuration options + $configurationUserFields = []; + if (isset($this->hostedDomain)) { + $configurationUserFields[] = 'domain'; + } + $fields = array_merge($this->defaultUserFields, $this->userFields, $configurationUserFields); + return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([ + 'fields' => implode(',', $fields), + 'alt' => 'json', + ]); + } + + protected function getAuthorizationParameters(array $options) + { + $params = array_merge( + parent::getAuthorizationParameters($options), + array_filter([ + 'hd' => $this->hostedDomain, + 'access_type' => $this->accessType, + // if the user is logged in with more than one account ask which one to use for the login! + 'authuser' => '-1' + ]) + ); + + return $params; + } + + protected function getDefaultScopes() + { + return [ + 'email', + 'openid', + 'profile', + ]; + } + + protected function getScopeSeparator() + { + return ' '; + } + + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data['error'])) { + $code = 0; + $error = $data['error']; + + if (is_array($error)) { + $code = $error['code']; + $error = $error['message']; + } + + throw new IdentityProviderException($error, $code, $data); + } + } + + protected function createResourceOwner(array $response, AccessToken $token) + { + $user = new GoogleUser($response); + // Validate hosted domain incase the user edited the initial authorization code grant request + if ($this->hostedDomain === '*') { + if (empty($user->getHostedDomain())) { + throw HostedDomainException::notMatchingDomain($this->hostedDomain); + } + } elseif (!empty($this->hostedDomain) && $this->hostedDomain !== $user->getHostedDomain()) { + throw HostedDomainException::notMatchingDomain($this->hostedDomain); + } + + return $user; + } +} diff --git a/vendor/league/oauth2-google/src/Provider/GoogleUser.php b/vendor/league/oauth2-google/src/Provider/GoogleUser.php new file mode 100644 index 0000000000..23f70aab1f --- /dev/null +++ b/vendor/league/oauth2-google/src/Provider/GoogleUser.php @@ -0,0 +1,125 @@ +response = $response; + } + + public function getId() + { + if (array_key_exists('sub', $this->response)) { + return $this->response['sub']; + } + return $this->response['id']; + } + + /** + * Get preferred display name. + * + * @return string + */ + public function getName() + { + if (array_key_exists('name', $this->response) && is_string($this->response['name'])) { + return $this->response['name']; + } + return $this->response['displayName']; + } + + /** + * Get preferred first name. + * + * @return string + */ + public function getFirstName() + { + if (array_key_exists('given_name', $this->response)) { + return $this->response['given_name']; + } + return $this->response['name']['givenName']; + } + + /** + * Get preferred last name. + * + * @return string + */ + public function getLastName() + { + if (array_key_exists('family_name', $this->response)) { + return $this->response['family_name']; + } + return $this->response['name']['familyName']; + } + + /** + * Get email address. + * + * @return string|null + */ + public function getEmail() + { + if (array_key_exists('email', $this->response)) { + return $this->response['email']; + } + if (!empty($this->response['emails'])) { + return $this->response['emails'][0]['value']; + } + return null; + } + + /** + * Get hosted domain. + * + * @return string|null + */ + public function getHostedDomain() + { + if (array_key_exists('hd', $this->response)) { + return $this->response['hd']; + } + if (array_key_exists('domain', $this->response)) { + return $this->response['domain']; + } + + return null; + } + + /** + * Get avatar image URL. + * + * @return string|null + */ + public function getAvatar() + { + if (array_key_exists('picture', $this->response)) { + return $this->response['picture']; + } + if (!empty($this->response['image']['url'])) { + return $this->response['image']['url']; + } + return null; + } + + /** + * Get user data as an array. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/vendor/monolog/monolog/.php_cs b/vendor/monolog/monolog/.php_cs new file mode 100644 index 0000000000..366ccd08b6 --- /dev/null +++ b/vendor/monolog/monolog/.php_cs @@ -0,0 +1,59 @@ + + +For the full copyright and license information, please view the LICENSE +file that was distributed with this source code. +EOF; + +$finder = Symfony\CS\Finder::create() + ->files() + ->name('*.php') + ->exclude('Fixtures') + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') +; + +return Symfony\CS\Config::create() + ->setUsingCache(true) + //->setUsingLinter(false) + ->setRiskyAllowed(true) + ->setRules(array( + '@PSR2' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_return' => true, + 'header_comment' => array('header' => $header), + 'include' => true, + 'long_array_syntax' => true, + 'method_separation' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_blank_lines_between_uses' => true, + 'no_duplicate_semicolons' => true, + 'no_extra_consecutive_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_unused_imports' => true, + 'object_operator_without_whitespace' => true, + 'phpdoc_align' => true, + 'phpdoc_indent' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_type_to_var' => true, + 'psr0' => true, + 'single_blank_line_before_namespace' => true, + 'spaces_cast' => true, + 'standardize_not_equals' => true, + 'ternary_operator_spaces' => true, + 'trailing_comma_in_multiline_array' => true, + 'whitespacy_lines' => true, + )) + ->finder($finder) +; diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md new file mode 100644 index 0000000000..cd1142d15b --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.md @@ -0,0 +1,342 @@ +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occuring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputing API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000000..16473219bf --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011-2016 Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md new file mode 100644 index 0000000000..7d8ade5268 --- /dev/null +++ b/vendor/monolog/monolog/README.md @@ -0,0 +1,95 @@ +# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. + +### Author + +Jordi Boggiano - -
    +See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +### License + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000000..3b0c880557 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,66 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5", + "graylog2/gelf-php": "~1.0", + "sentry/sentry": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "doctrine/couchdb": "~1.0@dev", + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "php-amqplib/php-amqplib": "~2.4", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit-mock-objects": "2.3.0", + "jakub-onderka/php-parallel-lint": "0.9" + }, + "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis", + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "sentry/sentry": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "php-console/php-console": "Allow sending log messages to Google Chrome" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor", + "phpunit" + ] + } +} diff --git a/vendor/monolog/monolog/doc/01-usage.md b/vendor/monolog/monolog/doc/01-usage.md new file mode 100644 index 0000000000..8e2551f38d --- /dev/null +++ b/vendor/monolog/monolog/doc/01-usage.md @@ -0,0 +1,231 @@ +# Using Monolog + +- [Installation](#installation) +- [Core Concepts](#core-concepts) +- [Log Levels](#log-levels) +- [Configuring a logger](#configuring-a-logger) +- [Adding extra data in the records](#adding-extra-data-in-the-records) +- [Leveraging channels](#leveraging-channels) +- [Customizing the log format](#customizing-the-log-format) + +## Installation + +Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog)) +and as such installable via [Composer](http://getcomposer.org/). + +```bash +composer require monolog/monolog +``` + +If you do not use Composer, you can grab the code from GitHub, and use any +PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader)) +to load Monolog classes. + +## Core Concepts + +Every `Logger` instance has a channel (name) and a stack of handlers. Whenever +you add a record to the logger, it traverses the handler stack. Each handler +decides whether it fully handled the record, and if so, the propagation of the +record ends there. + +This allows for flexible logging setups, for example having a `StreamHandler` at +the bottom of the stack that will log anything to disk, and on top of that add +a `MailHandler` that will send emails only when an error message is logged. +Handlers also have a `$bubble` property which defines whether they block the +record or not if they handled it. In this example, setting the `MailHandler`'s +`$bubble` argument to false means that records handled by the `MailHandler` will +not propagate to the `StreamHandler` anymore. + +You can create many `Logger`s, each defining a channel (e.g.: db, request, +router, ..) and each of them combining various handlers, which can be shared +or not. The channel is reflected in the logs and allows you to easily see or +filter records. + +Each Handler also has a Formatter, a default one with settings that make sense +will be created if you don't set one. The formatters normalize and format +incoming records so that they can be used by the handlers to output useful +information. + +Custom severity levels are not available. Only the eight +[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice, +warning, error, critical, alert, emergency) are present for basic filtering +purposes, but for sorting and other use cases that would require +flexibility, you should add Processors to the Logger that can add extra +information (tags, user ip, ..) to the records before they are handled. + +## Log Levels + +Monolog supports the logging levels described by [RFC 5424](http://tools.ietf.org/html/rfc5424). + +- **DEBUG** (100): Detailed debug information. + +- **INFO** (200): Interesting events. Examples: User logs in, SQL logs. + +- **NOTICE** (250): Normal but significant events. + +- **WARNING** (300): Exceptional occurrences that are not errors. Examples: + Use of deprecated APIs, poor use of an API, undesirable things that are not + necessarily wrong. + +- **ERROR** (400): Runtime errors that do not require immediate action but + should typically be logged and monitored. + +- **CRITICAL** (500): Critical conditions. Example: Application component + unavailable, unexpected exception. + +- **ALERT** (550): Action must be taken immediately. Example: Entire website + down, database unavailable, etc. This should trigger the SMS alerts and wake + you up. + +- **EMERGENCY** (600): Emergency: system is unusable. + +## Configuring a logger + +Here is a basic setup to log to a file and to firephp on the DEBUG level: + +```php +pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG)); +$logger->pushHandler(new FirePHPHandler()); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +Let's explain it. The first step is to create the logger instance which will +be used in your code. The argument is a channel name, which is useful when +you use several loggers (see below for more details about it). + +The logger itself does not know how to handle a record. It delegates it to +some handlers. The code above registers two handlers in the stack to allow +handling records in two different ways. + +Note that the FirePHPHandler is called first as it is added on top of the +stack. This allows you to temporarily add a logger with bubbling disabled if +you want to override other configured loggers. + +> If you use Monolog standalone and are looking for an easy way to +> configure many handlers, the [theorchard/monolog-cascade](https://github.com/theorchard/monolog-cascade) +> can help you build complex logging configs via PHP arrays, yaml or json configs. + +## Adding extra data in the records + +Monolog provides two different ways to add extra informations along the simple +textual message. + +### Using the logging context + +The first way is the context, allowing to pass an array of data along the +record: + +```php +addInfo('Adding a new user', array('username' => 'Seldaek')); +``` + +Simple handlers (like the StreamHandler for instance) will simply format +the array to a string but richer handlers can take advantage of the context +(FirePHP is able to display arrays in pretty way for instance). + +### Using processors + +The second way is to add extra data for all records by using a processor. +Processors can be any callable. They will get the record as parameter and +must return it after having eventually changed the `extra` part of it. Let's +write a processor adding some dummy data in the record: + +```php +pushProcessor(function ($record) { + $record['extra']['dummy'] = 'Hello world!'; + + return $record; +}); +``` + +Monolog provides some built-in processors that can be used in your project. +Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list. + +> Tip: processors can also be registered on a specific handler instead of + the logger to apply only for this handler. + +## Leveraging channels + +Channels are a great way to identify to which part of the application a record +is related. This is useful in big applications (and is leveraged by +MonologBundle in Symfony2). + +Picture two loggers sharing a handler that writes to a single log file. +Channels would allow you to identify the logger that issued every record. +You can easily grep through the log files filtering this or that channel. + +```php +pushHandler($stream); +$logger->pushHandler($firephp); + +// Create a logger for the security-related stuff with a different channel +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +$securityLogger->pushHandler($firephp); + +// Or clone the first one to only change the channel +$securityLogger = $logger->withName('security'); +``` + +## Customizing the log format + +In Monolog it's easy to customize the format of the logs written into files, +sockets, mails, databases and other handlers. Most of the handlers use the + +```php +$record['formatted'] +``` + +value to be automatically put into the log device. This value depends on the +formatter settings. You can choose between predefined formatter classes or +write your own (e.g. a multiline text file for human-readable output). + +To configure a predefined formatter class, just set it as the handler's field: + +```php +// the default date format is "Y-m-d H:i:s" +$dateFormat = "Y n j, g:i a"; +// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" +$output = "%datetime% > %level_name% > %message% %context% %extra%\n"; +// finally, create a formatter +$formatter = new LineFormatter($output, $dateFormat); + +// Create a handler +$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG); +$stream->setFormatter($formatter); +// bind it to a logger object +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +``` + +You may also reuse the same formatter between multiple handlers and share those +handlers between multiple loggers. + +[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) → diff --git a/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md b/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md new file mode 100644 index 0000000000..bea968ace8 --- /dev/null +++ b/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md @@ -0,0 +1,157 @@ +# Handlers, Formatters and Processors + +- [Handlers](#handlers) + - [Log to files and syslog](#log-to-files-and-syslog) + - [Send alerts and emails](#send-alerts-and-emails) + - [Log specific servers and networked logging](#log-specific-servers-and-networked-logging) + - [Logging in development](#logging-in-development) + - [Log to databases](#log-to-databases) + - [Wrappers / Special Handlers](#wrappers--special-handlers) +- [Formatters](#formatters) +- [Processors](#processors) +- [Third Party Packages](#third-party-packages) + +## Handlers + +### Log to files and syslog + +- _StreamHandler_: Logs records into any PHP stream, use this for log files. +- _RotatingFileHandler_: Logs records to a file and creates one logfile per day. + It will also delete files older than `$maxFiles`. You should use + [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile + setups though, this is just meant as a quick and dirty solution. +- _SyslogHandler_: Logs records to the syslog. +- _ErrorLogHandler_: Logs records to PHP's + [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function. + +### Send alerts and emails + +- _NativeMailerHandler_: Sends emails using PHP's + [`mail()`](http://php.net/manual/en/function.mail.php) function. +- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. +- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. +- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. +- _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account. +- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API. +- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook. +- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks. +- _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance. +- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks. +- _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message. + +### Log specific servers and networked logging + +- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this + for UNIX and TCP sockets. See an [example](sockets.md). +- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). +- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server. +- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server. +- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). +- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server. +- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application. +- _LogglyHandler_: Logs records to a [Loggly](http://www.loggly.com/) account. +- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account. +- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server. +- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account. + +### Logging in development + +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing + inline `console` messages within [FireBug](http://getfirebug.com/). +- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing + inline `console` messages within Chrome. +- _BrowserConsoleHandler_: Handler to send logs to browser's Javascript `console` with + no browser extension required. Most browsers supporting `console` API are supported. +- _PHPConsoleHandler_: Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing + inline `console` and notification popup messages within Chrome. + +### Log to databases + +- _RedisHandler_: Logs records to a [redis](http://redis.io) server. +- _MongoDBHandler_: Handler to write records in MongoDB via a + [Mongo](http://pecl.php.net/package/mongo) extension connection. +- _CouchDBHandler_: Logs records to a CouchDB server. +- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM. +- _ElasticSearchHandler_: Logs records to an Elastic Search server. +- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php). + +### Wrappers / Special Handlers + +- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as + parameter and will accumulate log records of all levels until a record + exceeds the defined severity level. At which point it delivers all records, + including those of lower severity, to the handler it wraps. This means that + until an error actually happens you will not see anything in your logs, but + when it happens you will have the full information, including debug and info + records. This provides you with all the information you need, but only when + you need it. +- _DeduplicationHandler_: Useful if you are sending notifications or emails + when critical errors occur. It takes a logger as parameter and will + accumulate log records of all levels until the end of the request (or + `flush()` is called). At that point it delivers all records to the handler + it wraps, but only if the records are unique over a given time period + (60seconds by default). If the records are duplicates they are simply + discarded. The main use of this is in case of critical failure like if your + database is unreachable for example all your requests will fail and that + can result in a lot of notifications being sent. Adding this handler reduces + the amount of notifications to a manageable level. +- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring + exceptions raised by each child handler. This allows you to ignore issues + where a remote tcp connection may have died but you do not want your entire + application to crash and may wish to continue to log to other handlers. +- _BufferHandler_: This handler will buffer all the log records it receives + until `close()` is called at which point it will call `handleBatch()` on the + handler it wraps with all the log messages at once. This is very useful to + send an email with all records at once for example instead of having one mail + for every log record. +- _GroupHandler_: This handler groups other handlers. Every record received is + sent to all the handlers it is configured with. +- _FilterHandler_: This handler only lets records of the given levels through + to the wrapped handler. +- _SamplingHandler_: Wraps around another handler and lets you sample records + if you only want to store some of them. +- _NullHandler_: Any record it can handle will be thrown away. This can be used + to put on top of an existing handler stack to disable it temporarily. +- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger +- _TestHandler_: Used for testing, it records everything that is sent to it and + has accessors to read out the information. +- _HandlerWrapper_: A simple handler wrapper you can inherit from to create + your own wrappers easily. + +## Formatters + +- _LineFormatter_: Formats a log record into a one-line string. +- _HtmlFormatter_: Used to format log records into a human readable html table, mainly suitable for emails. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. +- _ScalarFormatter_: Used to format log records into an associative array of scalar values. +- _JsonFormatter_: Encodes a log record into json. +- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. +- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. +- _GelfMessageFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. +- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest). +- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler. +- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler. +- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler. +- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler. + +## Processors + +- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`. +- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated. +- _WebProcessor_: Adds the current request URI, request method and client IP to a log record. +- _MemoryUsageProcessor_: Adds the current memory usage to a log record. +- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record. +- _ProcessIdProcessor_: Adds the process id to a log record. +- _UidProcessor_: Adds a unique identifier to a log record. +- _GitProcessor_: Adds the current git branch and commit to a log record. +- _TagProcessor_: Adds an array of predefined tags to a log record. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +← [Usage](01-usage.md) | [Utility classes](03-utilities.md) → diff --git a/vendor/monolog/monolog/doc/03-utilities.md b/vendor/monolog/monolog/doc/03-utilities.md new file mode 100644 index 0000000000..c62aa41611 --- /dev/null +++ b/vendor/monolog/monolog/doc/03-utilities.md @@ -0,0 +1,13 @@ +# Utilities + +- _Registry_: The `Monolog\Registry` class lets you configure global loggers that you + can then statically access from anywhere. It is not really a best practice but can + help in some older codebases or for ease of use. +- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register + a Logger instance as an exception handler, error handler or fatal error handler. +- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log + level is reached. +- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain + log level is reached, depending on which channel received the log record. + +← [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) | [Extending Monolog](04-extending.md) → diff --git a/vendor/monolog/monolog/doc/04-extending.md b/vendor/monolog/monolog/doc/04-extending.md new file mode 100644 index 0000000000..ebd9104db6 --- /dev/null +++ b/vendor/monolog/monolog/doc/04-extending.md @@ -0,0 +1,76 @@ +# Extending Monolog + +Monolog is fully extensible, allowing you to adapt your logger to your needs. + +## Writing your own handler + +Monolog provides many built-in handlers. But if the one you need does not +exist, you can write it and use it in your logger. The only requirement is +to implement `Monolog\Handler\HandlerInterface`. + +Let's write a PDOHandler to log records to a database. We will extend the +abstract class provided by Monolog to keep things DRY. + +```php +pdo = $pdo; + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if (!$this->initialized) { + $this->initialize(); + } + + $this->statement->execute(array( + 'channel' => $record['channel'], + 'level' => $record['level'], + 'message' => $record['formatted'], + 'time' => $record['datetime']->format('U'), + )); + } + + private function initialize() + { + $this->pdo->exec( + 'CREATE TABLE IF NOT EXISTS monolog ' + .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' + ); + $this->statement = $this->pdo->prepare( + 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' + ); + + $this->initialized = true; + } +} +``` + +You can now use this handler in your logger: + +```php +pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite'))); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +The `Monolog\Handler\AbstractProcessingHandler` class provides most of the +logic needed for the handler, including the use of processors and the formatting +of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``). + +← [Utility classes](03-utilities.md) diff --git a/vendor/monolog/monolog/doc/sockets.md b/vendor/monolog/monolog/doc/sockets.md new file mode 100644 index 0000000000..ea9cf0ea76 --- /dev/null +++ b/vendor/monolog/monolog/doc/sockets.md @@ -0,0 +1,39 @@ +Sockets Handler +=============== + +This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen) +or [pfsockopen](http://php.net/pfsockopen). + +Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening +the connections between requests. + +You can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP. + +Basic Example +------------- + +```php +setPersistent(true); + +// Now add the handler +$logger->pushHandler($handler, Logger::DEBUG); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); + +``` + +In this example, using syslog-ng, you should see the log on the log server: + + cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] [] + diff --git a/vendor/monolog/monolog/phpunit.xml.dist b/vendor/monolog/monolog/phpunit.xml.dist new file mode 100644 index 0000000000..20d82b631d --- /dev/null +++ b/vendor/monolog/monolog/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + tests/Monolog/ + + + + + + src/Monolog/ + + + + + + + diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000000..7bfcd833a5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Monolog\Handler\AbstractHandler; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + private $handleOnlyReportedErrors; + + private $hasFatalErrorHandler; + private $fatalLevel; + private $reservedMemory; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 + class_exists('\\Psr\\Log\\LogLevel', true); + + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level; + $this->hasFatalErrorHandler = true; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException($e) + { + $this->logger->log( + $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + + exit(255); + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { + return; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); + } + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { + $this->logger->log( + $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']) + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof AbstractHandler) { + $handler->close(); + } + } + } + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000000..9beda1e763 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file'], $record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file'], $record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 0000000000..4c556cf178 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected $index; + + /** + * @var string Elastic search document type + */ + protected $type; + + /** + * @param string $index Elastic Search index name + * @param string $type Elastic Search document type + */ + public function __construct($index, $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + * @return string + */ + public function getIndex() + { + return $this->index; + } + + /** + * Getter type + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param array $record Log message + * @return Document + */ + protected function getDocument($record) + { + $document = new Document(); + $document->setData($record); + $document->setType($this->type); + $document->setIndex($this->index); + + return $document; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 0000000000..5094af3c77 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + */ +class FlowdockFormatter implements FormatterInterface +{ + /** + * @var string + */ + private $source; + + /** + * @var string + */ + private $sourceEmail; + + /** + * @param string $source + * @param string $sourceEmail + */ + public function __construct($source, $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $tags = array( + '#logs', + '#' . strtolower($record['level_name']), + '#' . $record['channel'], + ); + + foreach ($record['extra'] as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record['level_name'], + $this->getShortMessage($record['message']) + ); + + $record['flowdock'] = array( + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record['message'], + 'tags' => $tags, + 'project' => $this->source, + ); + + return $record; + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + /** + * @param string $message + * + * @return string + */ + public function getShortMessage($message) + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 0000000000..02632bb56f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected $levelTag = false; + + public function __construct($levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = (bool) $levelTag; + } + + public function isUsingLevelsInTag() + { + return $this->levelTag; + } + + public function format(array $record) + { + $tag = $record['channel']; + if ($this->levelTag) { + $tag .= '.' . strtolower($record['level_name']); + } + + $message = array( + 'message' => $record['message'], + 'extra' => $record['extra'], + ); + + if (!$this->levelTag) { + $message['level'] = $record['level']; + $message['level_name'] = $record['level_name']; + } + + return json_encode(array($tag, $record['datetime']->getTimestamp(), $message)); + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000000..b5de751112 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000000..2c1b0e86c5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int max length per field + */ + protected $maxLength; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null) + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if (!isset($record['datetime'], $record['message'], $record['level'])) { + throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given'); + } + + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setHost($this->systemName) + ->setLevel($this->logLevels[$record['level']]); + + // message length + system name length + 200 for padding / metadata + $len = 200 + strlen((string) $record['message']) + strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(substr($record['message'], 0, $this->maxLength)); + } + + if (isset($record['channel'])) { + $message->setFacility($record['channel']); + } + if (isset($record['extra']['line'])) { + $message->setLine($record['extra']['line']); + unset($record['extra']['line']); + } + if (isset($record['extra']['file'])) { + $message->setFile($record['extra']['file']); + unset($record['extra']['file']); + } + + foreach ($record['extra'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength)); + break; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($record['context'] as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength)); + break; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (null === $message->getFile() && isset($record['context']['exception']['file'])) { + if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) { + $message->setFile($matches[1]); + $message->setLine($matches[2]); + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 0000000000..3eec95f6cc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected $logLevels = array( + Logger::DEBUG => '#cccccc', + Logger::INFO => '#468847', + Logger::NOTICE => '#3a87ad', + Logger::WARNING => '#c09853', + Logger::ERROR => '#f0ad4e', + Logger::CRITICAL => '#FF7708', + Logger::ALERT => '#C12A19', + Logger::EMERGENCY => '#000000', + ); + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + * @return string + */ + protected function addRow($th, $td = ' ', $escapeTd = true) + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
    '.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
    '; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + * @param int $level Error level + * @return string + */ + protected function addTitle($title, $level) + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

    '.$title.'

    '; + } + + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record) + { + $output = $this->addTitle($record['level_name'], $record['level']); + $output .= ''; + + $output .= $this->addRow('Message', (string) $record['message']); + $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat)); + $output .= $this->addRow('Channel', $record['channel']); + if ($record['context']) { + $embeddedTable = '
    '; + foreach ($record['context'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
    '; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if ($record['extra']) { + $embeddedTable = ''; + foreach ($record['extra'] as $key => $value) { + $embeddedTable .= $this->addRow($key, $this->convertToString($value)); + } + $embeddedTable .= '
    '; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return str_replace('\\/', '/', json_encode($data)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000000..0782f1499d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; +use Throwable; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + const BATCH_MODE_JSON = 1; + const BATCH_MODE_NEWLINES = 2; + + protected $batchMode; + protected $appendNewline; + + /** + * @var bool + */ + protected $includeStacktraces = false; + + /** + * @param int $batchMode + * @param bool $appendNewline + */ + public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + * + * @return int + */ + public function getBatchMode() + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + * + * @return bool + */ + public function isAppendingNewlines() + { + return $this->appendNewline; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + switch ($this->batchMode) { + case static::BATCH_MODE_NEWLINES: + return $this->formatBatchNewlines($records); + + case static::BATCH_MODE_JSON: + default: + return $this->formatBatchJson($records); + } + } + + /** + * @param bool $include + */ + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + } + + /** + * Return a JSON-encoded array of records. + * + * @param array $records + * @return string + */ + protected function formatBatchJson(array $records) + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @param array $records + * @return string + */ + protected function formatBatchNewlines(array $records) + { + $instance = $this; + + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + array_walk($records, function (&$value, $key) use ($instance) { + $value = $instance->format($value); + }); + $this->appendNewline = $oldNewline; + + return implode("\n", $records); + } + + /** + * Normalizes given $data. + * + * @param mixed $data + * + * @return mixed + */ + protected function normalize($data) + { + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items, aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof Exception || $data instanceof Throwable) { + return $this->normalizeException($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @param Exception|Throwable $e + * + * @return array + */ + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($this->includeStacktraces) { + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->normalize($frame); + } + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000000..d3e209e6cf --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + protected $allowInlineLineBreaks; + protected $ignoreEmptyContextAndExtra; + protected $includeStacktraces; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * @param bool $ignoreEmptyContextAndExtra + */ + public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + parent::__construct($dateFormat); + } + + public function includeStacktraces($include = true) + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + } + } + + public function allowInlineLineBreaks($allow = true) + { + $this->allowInlineLineBreaks = $allow; + } + + public function ignoreEmptyContextAndExtra($ignore = true) + { + $this->ignoreEmptyContextAndExtra = $ignore; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (empty($vars['context'])) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (empty($vars['extra'])) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + public function stringify($value) + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof \Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $previousText = ''; + if ($previous = $e->getPrevious()) { + do { + $previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + $str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')'; + if ($this->includeStacktraces) { + $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n"; + } + + return $str; + } + + protected function convertToString($data) + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data, true); + } + + return str_replace('\\/', '/', @json_encode($data)); + } + + protected function replaceNewlines($str) + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + return str_replace(array('\r', '\n'), array("\r", "\n"), $str); + } + + return $str; + } + + return str_replace(array("\r\n", "\r", "\n"), ' ', $str); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 0000000000..401859bba6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + * + * @param int $batchMode + */ + public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function format(array $record) + { + if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) { + $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO"); + // TODO 2.0 unset the 'datetime' parameter, retained for BC + } + + return parent::format($record); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000000..8f83bec041 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + const V0 = 0; + const V1 = 1; + + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @var int logstash format version to use + */ + protected $version; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + * @param int $version the logstash format version to use, defaults to 0 + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0) + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->version = $version; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + + if ($this->version === self::V1) { + $message = $this->formatV1($record); + } else { + $message = $this->formatV0($record); + } + + return $this->toJson($message) . "\n"; + } + + protected function formatV0(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@source' => $this->systemName, + '@fields' => array(), + ); + if (isset($record['message'])) { + $message['@message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['@tags'] = array($record['channel']); + $message['@fields']['channel'] = $record['channel']; + } + if (isset($record['level'])) { + $message['@fields']['level'] = $record['level']; + } + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + } + + return $message; + } + + protected function formatV1(array $record) + { + if (empty($record['datetime'])) { + $record['datetime'] = gmdate('c'); + } + $message = array( + '@timestamp' => $record['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ); + if (isset($record['message'])) { + $message['message'] = $record['message']; + } + if (isset($record['channel'])) { + $message['type'] = $record['channel']; + $message['channel'] = $record['channel']; + } + if (isset($record['level_name'])) { + $message['level'] = $record['level_name']; + } + if ($this->applicationName) { + $message['type'] = $this->applicationName; + } + if (!empty($record['extra'])) { + foreach ($record['extra'] as $key => $val) { + $message[$this->extraPrefix . $key] = $val; + } + } + if (!empty($record['context'])) { + foreach ($record['context'] as $key => $val) { + $message[$this->contextPrefix . $key] = $val; + } + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 0000000000..eb067bb726 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private $exceptionTraceAsString; + private $maxNestingLevel; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = (bool) $exceptionTraceAsString; + } + + /** + * {@inheritDoc} + */ + public function format(array $record) + { + return $this->formatArray($record); + } + + /** + * {@inheritDoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function formatArray(array $record, $nestingLevel = 0) + { + if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) { + foreach ($record as $name => $value) { + if ($value instanceof \DateTime) { + $record[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Exception) { + $record[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $record[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value)) { + $record[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + } else { + $record = '[...]'; + } + + return $record; + } + + protected function formatObject($value, $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = get_class($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + protected function formatException(\Exception $exception, $nestingLevel) + { + $formattedException = array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ); + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTime $value, $nestingLevel) + { + return new \MongoDate($value->getTimestamp()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000000..d44148823a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + // TODO 2.0 only check for Throwable + if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) { + return $this->normalizeException($data); + } + + // non-serializable objects that implement __toString stringified + if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) { + $value = $data->__toString(); + } else { + // the rest is json-serialized in some way + $value = $this->toJson($data, true); + } + + return sprintf("[object] (%s: %s)", get_class($data), $value); + } + + if (is_resource($data)) { + return sprintf('[resource] (%s)', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException($e) + { + // TODO 2.0 only check for Throwable + if (!$e instanceof Exception && !$e instanceof \Throwable) { + throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e)); + } + + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + $data['detail'] = $e->detail; + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } elseif (isset($frame['function']) && $frame['function'] === '{closure}') { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $frame['function']; + } else { + // We should again normalize the frames, because it might contain invalid items + $data['trace'][] = $this->toJson($this->normalize($frame), true); + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param bool $ignoreErrors + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string + */ + protected function toJson($data, $ignoreErrors = false) + { + // suppress json_encode errors since it's twitchy with some inputs + if ($ignoreErrors) { + return @$this->jsonEncode($data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $json = $this->handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * @param mixed $data + * @return string JSON encoded data or null on failure + */ + private function jsonEncode($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * inital error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + private function handleJsonError($code, $data) + { + if ($code !== JSON_ERROR_UTF8) { + $this->throwEncodeError($code, $data); + } + + if (is_string($data)) { + $this->detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, array($this, 'detectAndCleanUtf8')); + } else { + $this->throwEncodeError($code, $data); + } + + $json = $this->jsonEncode($data); + + if ($json === false) { + $this->throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private function throwEncodeError($code, $data) + { + switch ($code) { + case JSON_ERROR_DEPTH: + $msg = 'Maximum stack depth exceeded'; + break; + case JSON_ERROR_STATE_MISMATCH: + $msg = 'Underflow or the modes mismatch'; + break; + case JSON_ERROR_CTRL_CHAR: + $msg = 'Unexpected control character found'; + break; + case JSON_ERROR_UTF8: + $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; + break; + default: + $msg = 'Unknown error'; + } + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed &$data Input to check and convert if needed + * @private + */ + public function detectAndCleanUtf8(&$data) + { + if (is_string($data) && !preg_match('//u', $data)) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) { return utf8_encode($m[0]); }, + $data + ); + $data = str_replace( + array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), + array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + $data + ); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 0000000000..5d345d53c6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats data into an associative array of scalar values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + foreach ($record as $key => $value) { + $record[$key] = $this->normalizeValue($value); + } + + return $record; + } + + /** + * @param mixed $value + * @return mixed + */ + protected function normalizeValue($value) + { + $normalized = $this->normalize($value); + + if (is_array($normalized) || is_object($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000000..654710a8d1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + const TABLE = 'table'; + + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (isset($record['context'][self::TABLE])) { + $type = 'TABLE'; + $label = $record['channel'] .': '. $record['message']; + $message = $record['context'][self::TABLE]; + } else { + $type = $this->logLevels[$record['level']]; + $label = $record['channel']; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000000..758a425c3b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param int|string $level Level or level name + * @return self + */ + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param Boolean $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return Boolean true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } catch (\Throwable $e) { + // do nothing + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000000..6f18f72e13 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 0000000000..e2b2832d04 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + protected $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000000..e5a46bc0d5 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var AMQPExchange|AMQPChannel $exchange + */ + protected $exchange; + + /** + * @var string + */ + protected $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + if ($exchange instanceof AMQPExchange) { + $exchange->setName($exchangeName); + } elseif ($exchange instanceof AMQPChannel) { + $this->exchangeName = $exchangeName; + } else { + throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required'); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $this->exchange->publish( + $data, + $routingKey, + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records) + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + * + * @param array $record + * @return string + */ + protected function getRoutingKey(array $record) + { + $routingKey = sprintf( + '%s.%s', + // TODO 2.0 remove substr call + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + return strtolower($routingKey); + } + + /** + * @param string $data + * @return AMQPMessage + */ + private function createAmqpMessage($data) + { + return new AMQPMessage( + (string) $data, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 0000000000..b3a21bd408 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static $initialized = false; + protected static $records = array(); + + /** + * {@inheritDoc} + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + // Accumulate records + self::$records[] = $record; + + // Register shutdown handler if not already done + if (!self::$initialized) { + self::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send() + { + $format = self::getResponseFormat(); + if ($format === 'unknown') { + return; + } + + if (count(self::$records)) { + if ($format === 'html') { + self::writeOutput(''); + } elseif ($format === 'js') { + self::writeOutput(self::generateScript()); + } + self::reset(); + } + } + + /** + * Forget all logged records + */ + public static function reset() + { + self::$records = array(); + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction() + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + } + } + + /** + * Wrapper for echo to allow overriding + * + * @param string $str + */ + protected static function writeOutput($str) + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + */ + protected static function getResponseFormat() + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { + return 'js'; + } + if (stripos($header, 'text/html') === false) { + return 'unknown'; + } + break; + } + } + + return 'html'; + } + + private static function generateScript() + { + $script = array(); + foreach (self::$records as $record) { + $context = self::dump('Context', $record['context']); + $extra = self::dump('Extra', $record['extra']); + + if (empty($context) && empty($extra)) { + $script[] = self::call_array('log', self::handleStyles($record['formatted'])); + } else { + $script = array_merge($script, + array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))), + $context, + $extra, + array(self::call('groupEnd')) + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function handleStyles($formatted) + { + $args = array(self::quote('font-weight: normal')); + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); + $args[] = '"font-weight: normal"'; + + $pos = $match[0][1]; + $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + } + + array_unshift($args, self::quote($format)); + + return $args; + } + + private static function handleCustomStyles($style, $string) + { + static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); + static $labels = array(); + + return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + } + + private static function dump($title, array $dict) + { + $script = array(); + $dict = array_filter($dict); + if (empty($dict)) { + return $script; + } + $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (empty($value)) { + $value = self::quote(''); + } + $script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value); + } + + return $script; + } + + private static function quote($arg) + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + private static function call() + { + $args = func_get_args(); + $method = array_shift($args); + + return self::call_array($method, $args); + } + + private static function call_array($method, array $args) + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000000..72f895357a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + protected $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear() + { + $this->bufferSize = 0; + $this->buffer = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000000..785cb0c960 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * + * @var Boolean + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + } + + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 240 * 1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + } + + if (trim($data) !== '') { + $this->sendHeader(self::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000000..cc98697199 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ), + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000000..96b3ca0c57 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection; + private $httpConnection; + private $scheme; + private $host; + private $port; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfo = parse_url($url); + + if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + * @throws \LogicException when no curl extension + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + if ($this->scheme === 'http') { + $this->writeHttp(json_encode($data)); + } else { + $this->writeUdp(json_encode($data)); + } + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + )); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 0000000000..48d30b3586 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +class Util +{ + private static $retriableErrorCodes = array( + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ); + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param resource $ch curl handler + * @throws \RuntimeException + */ + public static function execute($ch, $retries = 5, $closeAfterDone = true) + { + while ($retries--) { + if (curl_exec($ch) === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + break; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 0000000000..7778c22a68 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + /** + * @var string + */ + protected $deduplicationStore; + + /** + * @var int + */ + protected $deduplicationLevel; + + /** + * @var int + */ + protected $time; + + /** + * @var bool + */ + private $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string $deduplicationStore The file/path where the deduplication log should be kept + * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + { + parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record['level'] >= $this->deduplicationLevel) { + + $passthru = $passthru || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + private function isDuplicate(array $record) + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record['datetime']->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs() + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + flock($handle, LOCK_EX); + $validLogs = array(); + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + private function appendRecord(array $record) + { + file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000000..b91ffec905 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 0000000000..237b71f616 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Logger; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + /** + * @var DynamoDbClient + */ + protected $client; + + /** + * @var string + */ + protected $table; + + /** + * @var int + */ + protected $version; + + /** + * @var Marshaler + */ + protected $marshaler; + + /** + * @param DynamoDbClient $client + * @param string $table + * @param int $level + * @param bool $bubble + */ + public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true) + { + if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) { + $this->version = 3; + $this->marshaler = new Marshaler; + } else { + $this->version = 2; + } + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $filtered = $this->filterEmptyFields($record['formatted']); + if ($this->version === 3) { + $formatted = $this->marshaler->marshalItem($filtered); + } else { + $formatted = $this->client->formatAttributes($filtered); + } + + $this->client->putItem(array( + 'TableName' => $this->table, + 'Item' => $formatted, + )); + } + + /** + * @param array $record + * @return array + */ + protected function filterEmptyFields(array $record) + { + return array_filter($record, function ($value) { + return !empty($value) || false === $value || 0 === $value; + }); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php new file mode 100644 index 0000000000..8196740691 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticSearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticSearchHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var array Handler config options + */ + protected $options = array(); + + /** + * @param Client $client Elastica Client object + * @param array $options Handler configuration + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + array( + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ), + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->bulkSend(array($record['formatted'])); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + } + + /** + * Getter options + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * @param array $documents + * @throws \RuntimeException + */ + protected function bulkSend(array $documents) + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000000..1447a584b3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + const OPERATING_SYSTEM = 0; + const SAPI = 4; + + protected $messageType; + protected $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + */ + public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes())) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return array With all available types + */ + public static function getAvailableTypes() + { + return array( + self::OPERATING_SYSTEM, + self::SAPI, + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if ($this->expandNewlines) { + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } else { + error_log((string) $record['formatted'], $this->messageType); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 0000000000..2a0f7fd15f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends AbstractHandler +{ + /** + * Handler or factory callable($record, $this) + * + * @var callable|\Monolog\Handler\HandlerInterface + */ + protected $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var int[] + */ + protected $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + * + * @var Boolean + */ + protected $bubble; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $this). + * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * @return array + */ + public function getAcceptedLevels() + { + return array_flip($this->acceptedLevels); + } + + /** + * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + */ + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) { + return $level >= $minLevelOrList && $level <= $maxLevel; + })); + } + $this->acceptedLevels = array_flip($acceptedLevels); + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return isset($this->acceptedLevels[$record['level']]); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $filtered = array(); + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + $this->handler->handleBatch($filtered); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000000..c3e42efefa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return Boolean + */ + public function isHandlerActivated(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000000..2a2a64d940 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000000..6e630852fc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Logger; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000000..d1dcaacf01 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + protected $passthruLevel; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate() + { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + $record = end($this->buffer) ?: null; + + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (null !== $this->passthruLevel) { + $level = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, function ($record) use ($level) { + return $record['level'] >= $level; + }); + if (count($this->buffer) > 0) { + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + } + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + public function reset() + { + $this->buffering = true; + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear() + { + $this->buffer = array(); + $this->reset(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000000..fee4795088 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + if (!self::$sendHeaders) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 0000000000..c43c0134ff --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + const FLEEP_HOST = 'fleep.io'; + + const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @throws MissingExtensionException + */ + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; + parent::__construct($connectionString, $level, $bubble); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'message' => $record['formatted'], + ); + + return http_build_query($dataArray); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 0000000000..dd9a361c2a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + */ +class FlowdockHandler extends SocketHandler +{ + /** + * @var string + */ + protected $apiToken; + + /** + * @param string $apiToken + * @param bool|int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + $this->apiToken = $apiToken; + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + return json_encode($record['formatted']['flowdock']); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000000..d3847d828f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\IMessagePublisher; +use Gelf\PublisherInterface; +use Gelf\Publisher; +use InvalidArgumentException; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Publisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) { + throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance'); + } + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->publisher = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000000..663f5a923d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $processed[] = call_user_func($processor, $record); + } + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + foreach ($this->handlers as $handler) { + $handler->setFormatter($formatter); + } + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000000..d920c4ba04 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record Partial log record containing only a level key + * + * @return Boolean + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 0000000000..e540d80f40 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(array $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface +{ + /** + * @var HandlerInterface + */ + protected $handler; + + /** + * HandlerWrapper constructor. + * @param HandlerInterface $handler + */ + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + return $this->handler->handle($record); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + return $this->handler->handleBatch($records); + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + $this->handler->pushProcessor($callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + return $this->handler->popProcessor(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->handler->setFormatter($formatter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->handler->getFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 0000000000..73049f369b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * Use API version 1 + */ + const API_V1 = 'v1'; + + /** + * Use API version v2 + */ + const API_V2 = 'v2'; + + /** + * The maximum allowed length for the name used in the "from" field. + */ + const MAXIMUM_NAME_LENGTH = 15; + + /** + * The maximum allowed length for the message. + */ + const MAXIMUM_MESSAGE_LENGTH = 9500; + + /** + * @var string + */ + private $token; + + /** + * @var string + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var bool + */ + private $notify; + + /** + * @var string + */ + private $format; + + /** + * @var string + */ + private $host; + + /** + * @var string + */ + private $version; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field. + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useSSL Whether to connect via SSL. + * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) + * @param string $host The HipChat server hostname. + * @param string $version The HipChat API version (default HipChatHandler::API_V1) + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) + { + if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { + throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); + } + + $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + $this->format = $format; + $this->host = $host; + $this->version = $version; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'notify' => $this->version == self::API_V1 ? + ($this->notify ? 1 : 0) : + ($this->notify ? 'true' : 'false'), + 'message' => $record['formatted'], + 'message_format' => $this->format, + 'color' => $this->getAlertColor($record['level']), + ); + + if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { + if (function_exists('mb_substr')) { + $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } else { + $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; + } + } + + // if we are using the legacy API then we need to send some additional information + if ($this->version == self::API_V1) { + $dataArray['room_id'] = $this->room; + } + + // append the sender name if it is set + // always append it if we use the v1 api (it is required in v1) + if ($this->version == self::API_V1 || $this->name !== null) { + $dataArray['from'] = (string) $this->name; + } + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + if ($this->version == self::API_V1) { + $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; + } else { + // needed for rooms with special (spaces, etc) characters in the name + $room = rawurlencode($this->room); + $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; + } + + $header .= "Host: {$this->host}\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param int $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + if (count($records) == 0) { + return true; + } + + $batchRecords = $this->combineRecords($records); + + $handled = false; + foreach ($batchRecords as $batchRecord) { + if ($this->isHandling($batchRecord)) { + $this->write($batchRecord); + $handled = true; + } + } + + if (!$handled) { + return false; + } + + return false === $this->bubble; + } + + /** + * Combines multiple records into one. Error level of the combined record + * will be the highest level from the given records. Datetime will be taken + * from the first record. + * + * @param $records + * @return array + */ + private function combineRecords($records) + { + $batchRecord = null; + $batchRecords = array(); + $messages = array(); + $formattedMessages = array(); + $level = 0; + $levelName = null; + $datetime = null; + + foreach ($records as $record) { + $record = $this->processRecord($record); + + if ($record['level'] > $level) { + $level = $record['level']; + $levelName = $record['level_name']; + } + + if (null === $datetime) { + $datetime = $record['datetime']; + } + + $messages[] = $record['message']; + $messageStr = implode(PHP_EOL, $messages); + $formattedMessages[] = $this->getFormatter()->format($record); + $formattedMessageStr = implode('', $formattedMessages); + + $batchRecord = array( + 'message' => $messageStr, + 'formatted' => $formattedMessageStr, + 'context' => array(), + 'extra' => array(), + ); + + if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { + // Pop the last message and implode the remaining messages + $lastMessage = array_pop($messages); + $lastFormattedMessage = array_pop($formattedMessages); + $batchRecord['message'] = implode(PHP_EOL, $messages); + $batchRecord['formatted'] = implode('', $formattedMessages); + + $batchRecords[] = $batchRecord; + $messages = array($lastMessage); + $formattedMessages = array($lastFormattedMessage); + + $batchRecord = null; + } + } + + if (null !== $batchRecord) { + $batchRecords[] = $batchRecord; + } + + // Set the max level and datetime for all records + foreach ($batchRecords as &$batchRecord) { + $batchRecord = array_merge( + $batchRecord, + array( + 'level' => $level, + 'level_name' => $levelName, + 'datetime' => $datetime, + ) + ); + } + + return $batchRecords; + } + + /** + * Validates the length of a string. + * + * If the `mb_strlen()` function is available, it will use that, as HipChat + * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. + * + * Note that this might cause false failures in the specific case of using + * a valid name with less than 16 characters, but 16 or more bytes, on a + * system where `mb_strlen()` is unavailable. + * + * @param string $str + * @param int $length + * + * @return bool + */ + private function validateStringLength($str, $length) + { + if (function_exists('mb_strlen')) { + return (mb_strlen($str) <= $length); + } + + return (strlen($str) <= $length); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 0000000000..d60a3c8251 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private $eventName; + private $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true) + { + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function write(array $record) + { + $postData = array( + "value1" => $record["channel"], + "value2" => $record["level_name"], + "value3" => $record["message"], + ); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/json", + )); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 0000000000..494c605bc9 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + /** + * @var string + */ + protected $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param int $level The minimum logging level to trigger this handler + * @param bool $bubble Whether or not messages that are handled should bubble up the stack. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true) + { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80'; + parent::__construct($endpoint, $level, $bubble); + $this->logToken = $token; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + return $this->logToken . ' ' . $record['formatted']; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 0000000000..bcd62e1c55 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LogglyFormatter; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + const HOST = 'logs-01.loggly.com'; + const ENDPOINT_SINGLE = 'inputs'; + const ENDPOINT_BATCH = 'bulk'; + + protected $token; + + protected $tag = array(); + + public function __construct($token, $level = Logger::DEBUG, $bubble = true) + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + public function setTag($tag) + { + $tag = !empty($tag) ? $tag : array(); + $this->tag = is_array($tag) ? $tag : array($tag); + } + + public function addTag($tag) + { + if (!empty($tag)) { + $tag = is_array($tag) ? $tag : array($tag); + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + } + + protected function write(array $record) + { + $this->send($record["formatted"], self::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records) + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record['level'] >= $level); + }); + + if ($records) { + $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + } + } + + protected function send($data, $endpoint) + { + $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + + $headers = array('Content-Type: application/json'); + + if (!empty($this->tag)) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + Curl\Util::execute($ch); + } + + protected function getDefaultFormatter() + { + return new LogglyFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000000..9e23283852 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } + + protected function getHighestRecord(array $records) + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 0000000000..ab95924f9c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected $message; + protected $apiKey; + + /** + * @param string $apiKey A valid Mandrill API key + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + $message->setDate(time()); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ))); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000000..4724a7e2d0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000000..56fe755b96 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + protected $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { + throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if ($this->mongoCollection instanceof \MongoDB\Collection) { + $this->mongoCollection->insertOne($record["formatted"]); + } else { + $this->mongoCollection->save($record["formatted"]); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000000..d7807fd116 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var array + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * Optional headers for the message + * @var array + */ + protected $headers = array(); + + /** + * Optional parameters for the message + * @var array + */ + protected $parameters = array(); + + /** + * The wordwrap length for the message + * @var int + */ + protected $maxColumnWidth; + + /** + * The Content-type for the message + * @var string + */ + protected $contentType = 'text/plain'; + + /** + * The encoding for the message + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|array $headers Custom added headers + * @return self + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|array $parameters Custom added parameters + * @return self + */ + public function addParameter($parameters) + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, $this->maxColumnWidth); + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; + if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subject = $this->subject; + if ($records) { + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + } + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + /** + * @return string $contentType + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * @return string $encoding + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML + * messages. + * @return self + */ + public function setContentType($contentType) + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + /** + * @param string $encoding + * @return self + */ + public function setEncoding($encoding) + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000000..6718e9e09d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var string + */ + protected $appName; + + /** + * Name of the current transaction + * + * @var string + */ + protected $transactionName; + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + * + * @var bool + */ + protected $explodeArrays; + + /** + * {@inheritDoc} + * + * @param string $appName + * @param bool $explodeArrays + * @param string $transactionName + */ + public function __construct( + $level = Logger::ERROR, + $bubble = true, + $appName = null, + $explodeArrays = false, + $transactionName = null + ) { + parent::__construct($level, $bubble); + + $this->appName = $appName; + $this->explodeArrays = $explodeArrays; + $this->transactionName = $transactionName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if ($transactionName = $this->getTransactionName($record['context'])) { + $this->setNewRelicTransactionName($transactionName); + unset($record['formatted']['context']['transaction_name']); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['formatted']['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) { + foreach ($record['formatted']['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) { + foreach ($record['formatted']['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param array $context + * @return null|string + */ + protected function getAppName(array $context) + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param array $context + * + * @return null|string + */ + protected function getTransactionName(array $context) + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + * + * @param string $appName + */ + protected function setNewRelicAppName($appName) + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + * + * @param string $transactionName + */ + protected function setNewRelicTransactionName($transactionName) + { + newrelic_name_transaction($transactionName); + } + + /** + * @param string $key + * @param mixed $value + */ + protected function setNewRelicParameter($key, $value) + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, @json_encode($value)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000000..4b8458833c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param int $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 0000000000..1f2076a49d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use PhpConsole\Connector; +use PhpConsole\Handler; +use PhpConsole\Helper; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + private $options = array( + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... + 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ); + + /** @var Connector */ + private $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @param int $level + * @param bool $bubble + * @throws Exception + */ + public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + private function initOptions(array $options) + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if ($wrongOptions) { + throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(Connector $connector = null) + { + if (!$connector) { + if ($this->options['dataStorage']) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = Handler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if ($this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if ($this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if ($this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if ($this->options['ipMasks']) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if ($this->options['headersLimit']) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector() + { + return $this->connector; + } + + public function getOptions() + { + return $this->options; + } + + public function handle(array $record) + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + protected function write(array $record) + { + if ($record['level'] < Logger::NOTICE) { + $this->handleDebugRecord($record); + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(array $record) + { + $tags = $this->getRecordTags($record); + $message = $record['message']; + if ($record['context']) { + $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context']))); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(array $record) + { + $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); + } + + private function handleErrorRecord(array $record) + { + $context = $record['context']; + + $this->connector->getErrorsDispatcher()->dispatchError( + isset($context['code']) ? $context['code'] : null, + isset($context['message']) ? $context['message'] : $record['message'], + isset($context['file']) ? $context['file'] : null, + isset($context['line']) ? $context['line'] : null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + private function getRecordTags(array &$record) + { + $tags = null; + if (!empty($record['context'])) { + $context = & $record['context']; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (!empty($context[$key])) { + $tags = $context[$key]; + if ($key === 0) { + array_shift($context); + } else { + unset($context[$key]); + } + break; + } + } + } + + return $tags ?: strtolower($record['level_name']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%message%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 0000000000..1ae85845d8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler +{ + /** + * PSR-3 compliant logger + * + * @var LoggerInterface + */ + protected $logger; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + + return false === $this->bubble; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000000..bba7200592 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + private $retry; + private $expire; + + private $highPriorityLevel; + private $emergencyLevel; + private $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private $parameterNames = array( + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ); + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var array + */ + private $sounds = array( + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ); + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; + $message = substr($message, 0, $maxMessageLength); + + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ); + + if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record['context'], $this->parameterNames); + $extra = array_intersect_key($record['extra'], $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } + + /** + * Use the formatted message? + * @param bool $value + */ + public function useFormattedMessage($value) + { + $this->useFormattedMessage = (boolean) $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 0000000000..53a8b391de --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server + * using raven-php (https://github.com/getsentry/raven-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + private $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var string should represent the current version of the calling + * software. Can be any string (git commit, version number) + */ + private $release; + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function ($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function ($highest, $record) { + if ($record['level'] > $highest['level']) { + return $record; + } + + return $highest; + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $previousUserContext = false; + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + $options['tags'] = array(); + if (!empty($record['extra']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); + unset($record['extra']['tags']); + } + if (!empty($record['context']['tags'])) { + $options['tags'] = array_merge($options['tags'], $record['context']['tags']); + unset($record['context']['tags']); + } + if (!empty($record['context']['fingerprint'])) { + $options['fingerprint'] = $record['context']['fingerprint']; + unset($record['context']['fingerprint']); + } + if (!empty($record['context']['logger'])) { + $options['logger'] = $record['context']['logger']; + unset($record['context']['logger']); + } else { + $options['logger'] = $record['channel']; + } + foreach ($this->getExtraParameters() as $key) { + foreach (array('extra', 'context') as $source) { + if (!empty($record[$source][$key])) { + $options[$key] = $record[$source][$key]; + unset($record[$source][$key]); + } + } + } + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + if (!empty($record['context']['user'])) { + $previousUserContext = $this->ravenClient->context->user; + $this->ravenClient->user_context($record['context']['user']); + unset($options['extra']['context']['user']); + } + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (!empty($this->release) && !isset($options['release'])) { + $options['release'] = $this->release; + } + + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + $options['extra']['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + } else { + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + if ($previousUserContext !== false) { + $this->ravenClient->user_context($previousUserContext); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } + + /** + * Gets extra parameters supported by Raven that can be found in "extra" and "context" + * + * @return array + */ + protected function getExtraParameters() + { + return array('checksum', 'release', 'event_id'); + } + + /** + * @param string $value + * @return self + */ + public function setRelease($value) + { + $this->release = $value; + + return $this; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000000..590f99657e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + protected $capSize; + + /** + * @param \Predis\Client|\Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $capSize Number of entries to limit list size to + */ + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if ($this->capSize) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + * + * @param array $record associative record array + * @return void + */ + protected function writeCapped(array $record) + { + if ($this->redisClient instanceof \Redis) { + $this->redisClient->multi() + ->rpush($this->redisKey, $record["formatted"]) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record["formatted"]); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 0000000000..6c8a3e3ede --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RollbarNotifier; +use Exception; +use Monolog\Logger; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarNotifier's report_message/report_exception methods. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + /** + * Rollbar notifier + * + * @var RollbarNotifier + */ + protected $rollbarNotifier; + + protected $levelMap = array( + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warning', + Logger::ERROR => 'error', + Logger::CRITICAL => 'critical', + Logger::ALERT => 'critical', + Logger::EMERGENCY => 'critical', + ); + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + * + * @var bool + */ + private $hasRecords = false; + + protected $initialized = false; + + /** + * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + { + $this->rollbarNotifier = $rollbarNotifier; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + $this->initialized = true; + } + + $context = $record['context']; + $payload = array(); + if (isset($context['payload'])) { + $payload = $context['payload']; + unset($context['payload']); + } + $context = array_merge($context, $record['extra'], array( + 'level' => $this->levelMap[$record['level']], + 'monolog_level' => $record['level_name'], + 'channel' => $record['channel'], + 'datetime' => $record['datetime']->format('U'), + )); + + if (isset($context['exception']) && $context['exception'] instanceof Exception) { + $payload['level'] = $context['level']; + $exception = $context['exception']; + unset($context['exception']); + + $this->rollbarNotifier->report_exception($exception, $context, $payload); + } else { + $this->rollbarNotifier->report_message( + $record['message'], + $context['level'], + $context, + $payload + ); + } + + $this->hasRecords = true; + } + + public function flush() + { + if ($this->hasRecords) { + $this->rollbarNotifier->flush(); + $this->hasRecords = false; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000000..3b60b3d15c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + const FILE_PER_DAY = 'Y-m-d'; + const FILE_PER_MONTH = 'Y-m'; + const FILE_PER_YEAR = 'Y'; + + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + + /** + * @param string $filename + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param Boolean $useLocking Try to lock log file before doing any writes + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat($filenameFormat, $dateFormat) + { + if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + trigger_error( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.', + E_USER_DEPRECATED + ); + } + if (substr_count($filenameFormat, '{date}') === 0) { + trigger_error( + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', + E_USER_DEPRECATED + ); + } + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], date($this->dateFormat)), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + array('{filename}', '{date}'), + array($fileInfo['filename'], '*'), + $fileInfo['dirname'] . '/' . $this->filenameFormat + ); + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 0000000000..9509ae3785 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler +{ + /** + * @var callable|HandlerInterface $handler + */ + protected $handler; + + /** + * @var int $factor + */ + protected $factor; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int $factor Sample factor + */ + public function __construct($handler, $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + + if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + } + + public function isHandling(array $record) + { + return $this->handler->isHandling($record); + } + + public function handle(array $record) + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + // The same logic as in FingersCrossedHandler + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->handler->handle($record); + } + + return false === $this->bubble; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 0000000000..38bc838aa4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,294 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + const COLOR_DANGER = 'danger'; + + const COLOR_WARNING = 'warning'; + + const COLOR_GOOD = 'good'; + + const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + * @var string|null + */ + private $channel; + + /** + * Name of a bot + * @var string|null + */ + private $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + * @var string + */ + private $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var array + */ + private $excludeFields; + + /** + * @var FormatterInterface + */ + private $formatter; + + /** + * @var NormalizerFormatter + */ + private $normalizerFormatter; + + public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) + { + $this->channel = $channel; + $this->username = $username; + $this->userIcon = trim($userIcon, ':'); + $this->useAttachment = $useAttachment; + $this->useShortAttachment = $useShortAttachment; + $this->includeContextAndExtra = $includeContextAndExtra; + $this->excludeFields = $excludeFields; + $this->formatter = $formatter; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + public function getSlackData(array $record) + { + $dataArray = array(); + $record = $this->excludeFields($record); + + if ($this->username) { + $dataArray['username'] = $this->username; + } + + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter && !$this->useAttachment) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp() + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); + } + + + if ($this->includeContextAndExtra) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + ucfirst($key), + $record[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) + ); + } + } + } + + $dataArray['attachments'] = array($attachment); + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon) { + if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { + $dataArray['icon_url'] = $this->userIcon; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + */ + public function getAttachmentColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return self::COLOR_DANGER; + case $level >= Logger::WARNING: + return self::COLOR_WARNING; + case $level >= Logger::INFO: + return self::COLOR_GOOD; + default: + return self::COLOR_DEFAULT; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * + * @return string + */ + public function stringify($fields) + { + $normalized = $this->normalizerFormatter->format($fields); + $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + + $hasSecondDimension = count(array_filter($normalized, 'is_array')); + $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); + + return $hasSecondDimension || $hasNonNumericKeys + ? json_encode($normalized, $prettyPrintFlag) + : json_encode($normalized); + } + + /** + * Sets the formatter + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * Generates attachment field + * + * @param string $title + * @param string|array $value\ + * + * @return array + */ + private function generateAttachmentField($title, $value) + { + $value = is_array($value) + ? sprintf('```%s```', $this->stringify($value)) + : $value; + + return array( + 'title' => $title, + 'value' => $value, + 'short' => false + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param array $data + * + * @return array + */ + private function generateAttachmentFields(array $data) + { + $fields = array(); + foreach ($data as $key => $value) { + $fields[] = $this->generateAttachmentField($key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @param array $record + * + * @return array + */ + private function excludeFields(array $record) + { + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$record; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 0000000000..3ac4d83687 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + * @var string + */ + private $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) + { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct('ssl://slack.com:443', $level, $bubble); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + + $this->token = $token; + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * Prepares content data + * + * @param array $record + * @return array + */ + protected function prepareContentData($record) + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = json_encode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite() + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function getAttachmentColor($level) + { + trigger_error( + 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->getAttachmentColor($level); + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string + * @deprecated Use underlying SlackRecord instead + */ + protected function stringify($fields) + { + trigger_error( + 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + + return $this->slackRecord->stringify($fields); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 0000000000..9a1bbb440a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + */ + public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = array()) + { + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields, + $this->formatter + ); + } + + public function getSlackRecord() + { + return $this->slackRecord; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $postData = $this->slackRecord->getSlackData($record); + $postString = json_encode($postData); + + $ch = curl_init(); + $options = array( + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Content-type: application/json'), + CURLOPT_POSTFIELDS => $postString + ); + if (defined('CURLOPT_SAFE_UPLOAD')) { + $options[CURLOPT_SAFE_UPLOAD] = true; + } + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php new file mode 100644 index 0000000000..baead52523 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through Slack's Slackbot + * + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + */ +class SlackbotHandler extends AbstractProcessingHandler +{ + /** + * The slug of the Slack team + * @var string + */ + private $slackTeam; + + /** + * Slackbot token + * @var string + */ + private $token; + + /** + * Slack channel name + * @var string + */ + private $channel; + + /** + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->slackTeam = $slackTeam; + $this->token = $token; + $this->channel = $channel; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $slackbotUrl = sprintf( + 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', + $this->slackTeam, + $this->token, + $this->channel + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slackbotUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); + + Curl\Util::execute($ch); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000000..7a61bf4e04 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,346 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $writingTimeout = 10; + private $lastSentBytes = null; + private $persistent = false; + private $errno; + private $errstr; + private $lastWritingAt; + + /** + * @param string $connectionString Socket connection string + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param bool $persistent + */ + public function setPersistent($persistent) + { + $this->persistent = (boolean) $persistent; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->writingTimeout = (float) $seconds; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return bool + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout() + { + return $this->writingTimeout; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return bool + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut($sent) + { + $writingTimeout = (int) floor($this->writingTimeout); + if (0 === $writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = time(); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((time() - $this->lastWritingAt) >= $writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000000..09a157387d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + private $dirCreated; + + /** + * @param resource|string $stream + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param Boolean $useLocking Try to lock log file before doing any writes + * + * @throws \Exception If a missing directory is not buildable + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } elseif (is_string($stream)) { + $this->url = $stream; + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * {@inheritdoc} + */ + public function close() + { + if ($this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + * + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (null === $this->url || '' === $this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->createDir(); + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url)); + } + } + + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($this->stream, LOCK_EX); + } + + $this->streamWrite($this->stream, $record); + + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + * @param array $record + */ + protected function streamWrite($stream, array $record) + { + fwrite($stream, (string) $record['formatted']); + } + + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + } + + /** + * @param string $stream + * + * @return null|string + */ + private function getDirFromStream($stream) + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return; + } + + private function createDir() + { + // Do not try to create dir if it has already been tried. + if ($this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($this->url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000000..72f44a5322 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Swift; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + private $messageTemplate; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Creates instance of Swift_Message to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return \Swift_Message + */ + protected function buildMessage($content, array $records) + { + $message = null; + if ($this->messageTemplate instanceof \Swift_Message) { + $message = clone $this->messageTemplate; + $message->generateId(); + } elseif (is_callable($this->messageTemplate)) { + $message = call_user_func($this->messageTemplate, $content, $records); + } + + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + } + + if ($records) { + $subjectFormatter = new LineFormatter($message->getSubject()); + $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); + } + + $message->setBody($content); + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + $message->setDate(time()); + } + + return $message; + } + + /** + * BC getter, to be removed in 2.0 + */ + public function __get($name) + { + if ($name === 'message') { + trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); + + return $this->buildMessage(null, array()); + } + + throw new \InvalidArgumentException('Invalid property '.$name); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000000..376bc3b24d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected $ident; + protected $logopts; + + /** + * @param string $ident + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 0000000000..3bff085bfa --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +class UdpSocket +{ + const DATAGRAM_MAX_LENGTH = 65023; + + protected $ip; + protected $port; + protected $socket; + + public function __construct($ip, $port = 514) + { + $this->ip = $ip; + $this->port = $port; + $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + } + + public function write($line, $header = "") + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close() + { + if (is_resource($this->socket)) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function send($chunk) + { + if (!is_resource($this->socket)) { + throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + } + socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage($line, $header) + { + $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . substr($line, 0, $chunkSize); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 0000000000..4718711b3b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + protected $socket; + protected $ident; + + /** + * @param string $host + * @param int $port + * @param mixed $facility + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + */ + public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php') + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + + $this->socket = new UdpSocket($host, $port ?: 514); + } + + protected function write(array $record) + { + $lines = $this->splitMessageIntoLines($record['formatted']); + + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close() + { + $this->socket->close(); + } + + private function splitMessageIntoLines($message) + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Make common syslog header (see rfc5424) + */ + protected function makeCommonSyslogHeader($severity) + { + $priority = $severity + $this->facility; + + if (!$pid = getmypid()) { + $pid = '-'; + } + + if (!$hostname = gethostname()) { + $hostname = '-'; + } + + return "<$priority>1 " . + $this->getDateTime() . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + + protected function getDateTime() + { + return date(\DateTime::RFC3339); + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket($socket) + { + $this->socket = $socket; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000000..e39cfc667a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency($record) + * @method bool hasAlert($record) + * @method bool hasCritical($record) + * @method bool hasError($record) + * @method bool hasWarning($record) + * @method bool hasNotice($record) + * @method bool hasInfo($record) + * @method bool hasDebug($record) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains($message) + * @method bool hasAlertThatContains($message) + * @method bool hasCriticalThatContains($message) + * @method bool hasErrorThatContains($message) + * @method bool hasWarningThatContains($message) + * @method bool hasNoticeThatContains($message) + * @method bool hasInfoThatContains($message) + * @method bool hasDebugThatContains($message) + * + * @method bool hasEmergencyThatMatches($message) + * @method bool hasAlertThatMatches($message) + * @method bool hasCriticalThatMatches($message) + * @method bool hasErrorThatMatches($message) + * @method bool hasWarningThatMatches($message) + * @method bool hasNoticeThatMatches($message) + * @method bool hasInfoThatMatches($message) + * @method bool hasDebugThatMatches($message) + * + * @method bool hasEmergencyThatPasses($message) + * @method bool hasAlertThatPasses($message) + * @method bool hasCriticalThatPasses($message) + * @method bool hasErrorThatPasses($message) + * @method bool hasWarningThatPasses($message) + * @method bool hasNoticeThatPasses($message) + * @method bool hasInfoThatPasses($message) + * @method bool hasDebugThatPasses($message) + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function clear() + { + $this->records = array(); + $this->recordsByLevel = array(); + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_array($record)) { + $record = $record['message']; + } + + return $this->hasRecordThatPasses(function ($rec) use ($record) { + return $rec['message'] === $record; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses($predicate, $level) + { + if (!is_callable($predicate)) { + throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); + } + + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant('Monolog\Logger::' . strtoupper($matches[2])); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + + return call_user_func_array(array($this, $genericMethod), $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 0000000000..2732ba3d62 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (\Exception $e) { + // What failure? + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000000..f22cf21874 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array( + Logger::DEBUG => 1, + Logger::INFO => 2, + Logger::NOTICE => 3, + Logger::WARNING => 4, + Logger::ERROR => 5, + Logger::CRITICAL => 6, + Logger::ALERT => 7, + Logger::EMERGENCY => 0, + ); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + } + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + $this->levelMap[$record['level']], + $record['message'], + $record['formatted'] + ); + } + + /** + * Write a record to Zend Monitor + * + * @param int $level + * @param string $message + * @param array $formatted + */ + protected function writeZendMonitorCustomEvent($level, $message, $formatted) + { + zend_monitor_custom_event($level, $message, $formatted); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000000..49d00af1f2 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,700 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + /** + * Logging levels from syslog protocol defined in RFC 5424 + * + * @var array $levels Logging levels + */ + protected static $levels = array( + self::DEBUG => 'DEBUG', + self::INFO => 'INFO', + self::NOTICE => 'NOTICE', + self::WARNING => 'WARNING', + self::ERROR => 'ERROR', + self::CRITICAL => 'CRITICAL', + self::ALERT => 'ALERT', + self::EMERGENCY => 'EMERGENCY', + ); + + /** + * @var \DateTimeZone + */ + protected static $timezone; + + /** + * @var string + */ + protected $name; + + /** + * The handler stack + * + * @var HandlerInterface[] + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var callable[] + */ + protected $processors; + + /** + * @var bool + */ + protected $microsecondTimestamps = true; + + /** + * @param string $name The logging channel + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->handlers = $handlers; + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + * + * @return static + */ + public function withName($name) + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + * @return $this + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param HandlerInterface[] $handlers + * @return $this + */ + public function setHandlers(array $handlers) + { + $this->handlers = array(); + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return HandlerInterface[] + */ + public function getHandlers() + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + * @return $this + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + */ + public function getProcessors() + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * Generating microsecond resolution timestamps by calling + * microtime(true), formatting the result via sprintf() and then parsing + * the resulting string via \DateTime::createFromFormat() can incur + * a measurable runtime overhead vs simple usage of DateTime to capture + * a second resolution timestamp in systems which generate a large number + * of log events. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps($micro) + { + $this->microsecondTimestamps = (bool) $micro; + } + + /** + * Adds a log record. + * + * @param int $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + $levelName = static::getLevelName($level); + + // check if any handler will handle this message so we can return early and save cycles + $handlerKey = null; + reset($this->handlers); + while ($handler = current($this->handlers)) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = key($this->handlers); + break; + } + + next($this->handlers); + } + + if (null === $handlerKey) { + return false; + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + // php7.1+ always has microseconds enabled, so we do not need this hack + if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { + $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); + } else { + $ts = new \DateTime(null, static::$timezone); + } + $ts->setTimezone(static::$timezone); + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $ts, + 'extra' => array(), + ); + + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + + return true; + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param int $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param string|int Level number (monolog) or name (PSR-3) + * @return int + */ + public static function toMonologLevel($level) + { + if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { + return constant(__CLASS__.'::'.strtoupper($level)); + } + + return $level; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param int $level + * @return Boolean + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + $level = static::toMonologLevel($level); + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Set the timezone to be used for the timestamp of log records. + * + * This is stored globally for all Logger instances + * + * @param \DateTimeZone $tz Timezone object + */ + public static function setTimezone(\DateTimeZone $tz) + { + self::$timezone = $tz; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 0000000000..1899400dca --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['git'] = self::getGitInfo(); + + return $record; + } + + private static function getGitInfo() + { + if (self::$cache) { + return self::$cache; + } + + $branches = `git branch -v --no-abbrev`; + if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = array( + 'branch' => $matches[1], + 'commit' => $matches[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000000..2c07caedea --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor +{ + private $level; + + private $skipClassesPartials; + + private $skipStackFramesCount; + + private $skipFunctions = array( + 'call_user_func', + 'call_user_func_array', + ); + + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + /* + * http://php.net/manual/en/function.debug-backtrace.php + * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. + * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. + */ + $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { + $i++; + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, + 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } + + private function isTraceClassOrSkippedFunction(array $trace, $index) + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000000..0543e9292f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_peak_usage'] = $formatted; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000000..85f9dc5e73 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct($realUsage = true, $useFormatting = true) + { + $this->realUsage = (boolean) $realUsage; + $this->useFormatting = (boolean) $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @param int $bytes + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + */ + protected function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000000..2783d656ba --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = $this->formatBytes($bytes); + + $record['extra']['memory_usage'] = $formatted; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 0000000000..7c07a7e994 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Logger; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor +{ + private $level; + private static $cache; + + public function __construct($level = Logger::DEBUG) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // return if the level is not high enough + if ($record['level'] < $this->level) { + return $record; + } + + $record['extra']['hg'] = self::getMercurialInfo(); + + return $record; + } + + private static function getMercurialInfo() + { + if (self::$cache) { + return self::$cache; + } + + $result = explode(' ', trim(`hg id -nb`)); + if (count($result) >= 3) { + return self::$cache = array( + 'branch' => $result[1], + 'revision' => $result[2], + ); + } + + return self::$cache = array(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000000..9d3f5590fd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = getmypid(); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000000..c2686ce5b1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements['{'.$key.'}'] = $val; + } elseif (is_object($val)) { + $replacements['{'.$key.'}'] = '[object '.get_class($val).']'; + } else { + $replacements['{'.$key.'}'] = '['.gettype($val).']'; + } + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 0000000000..7e2df2acb6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor +{ + private $tags; + + public function __construct(array $tags = array()) + { + $this->setTags($tags); + } + + public function addTags(array $tags = array()) + { + $this->tags = array_merge($this->tags, $tags); + } + + public function setTags(array $tags = array()) + { + $this->tags = $tags; + } + + public function __invoke(array $record) + { + $record['extra']['tags'] = $this->tags; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000000..812707cdba --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } + + /** + * @return string + */ + public function getUid() + { + return $this->uid; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000000..ea1d897829 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor +{ + /** + * @var array|\ArrayAccess + */ + protected $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected $extraFields = array( + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + ); + + /** + * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + */ + public function __construct($serverData = null, array $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + + if (null !== $extraFields) { + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = $this->appendExtraFields($record['extra']); + + return $record; + } + + /** + * @param string $extraName + * @param string $serverName + * @return $this + */ + public function addExtraField($extraName, $serverName) + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param array $extra + * @return array + */ + private function appendExtraFields(array $extra) + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + } + + if (isset($this->serverData['UNIQUE_ID'])) { + $extra['unique_id'] = $this->serverData['UNIQUE_ID']; + } + + return $extra; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 0000000000..159b751cdb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->addError('Sent to $api Logger instance'); + * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static $loggers = array(); + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, $name = null, $overwrite = false) + { + $name = $name ?: $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger) + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } else { + return isset(self::$loggers[$logger]); + } + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger) + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear() + { + self::$loggers = array(); + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function getInstance($name) + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param array $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic($name, $arguments) + { + return self::getInstance($name); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php new file mode 100644 index 0000000000..a9a3f301f8 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testHandleError() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new ErrorHandler($logger); + + $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false); + trigger_error('Foo', E_USER_ERROR); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasErrorRecords()); + trigger_error('Foo', E_USER_NOTICE); + $this->assertCount(2, $handler->getRecords()); + $this->assertTrue($handler->hasEmergencyRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php new file mode 100644 index 0000000000..71c4204614 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testDefaultFormat() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'unknown', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::CRITICAL, + 'level_name' => 'CRITICAL', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'test : 14', + 'error', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithoutContext() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::DEBUG, + 'level_name' => 'DEBUG', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + 'log', + 'unknown', + 'log', + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch + */ + public function testBatchFormatThrowException() + { + $formatter = new ChromePHPFormatter(); + $records = array( + array( + 'level' => Logger::INFO, + 'level_name' => 'INFO', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ), + array( + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log2', + ), + ); + + $this->assertEquals( + array( + array( + 'meh', + 'log', + 'unknown', + 'info', + ), + array( + 'foo', + 'log2', + 'unknown', + 'warn', + ), + ), + $formatter->formatBatch($records) + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php new file mode 100644 index 0000000000..90cc48dd7e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ElasticaFormatterTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ElasticaFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists("Elastica\Document")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::__construct + * @covers Monolog\Formatter\ElasticaFormatter::format + * @covers Monolog\Formatter\ElasticaFormatter::getDocument + */ + public function testFormat() + { + // test log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // expected values + $expected = $msg; + $expected['datetime'] = '1970-01-01T00:00:00.000000+00:00'; + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + // format log message + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $doc = $formatter->format($msg); + $this->assertInstanceOf('Elastica\Document', $doc); + + // Document parameters + $params = $doc->getParams(); + $this->assertEquals('my_index', $params['_index']); + $this->assertEquals('doc_type', $params['_type']); + + // Document data values + $data = $doc->getData(); + foreach (array_keys($expected) as $key) { + $this->assertEquals($expected[$key], $data[$key]); + } + } + + /** + * @covers Monolog\Formatter\ElasticaFormatter::getIndex + * @covers Monolog\Formatter\ElasticaFormatter::getType + */ + public function testGetters() + { + $formatter = new ElasticaFormatter('my_index', 'doc_type'); + $this->assertEquals('my_index', $formatter->getIndex()); + $this->assertEquals('doc_type', $formatter->getType()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php new file mode 100644 index 0000000000..1b2fd97a65 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/FlowdockFormatterTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FlowdockFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FlowdockFormatter::format + */ + public function testFormat() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $record = $this->getRecord(); + + $expected = array( + 'source' => 'test_source', + 'from_address' => 'source@test.com', + 'subject' => 'in test_source: WARNING - test', + 'content' => 'test', + 'tags' => array('#logs', '#warning', '#test'), + 'project' => 'test_source', + ); + $formatted = $formatter->format($record); + + $this->assertEquals($expected, $formatted['flowdock']); + } + + /** + * @ covers Monolog\Formatter\FlowdockFormatter::formatBatch + */ + public function testFormatBatch() + { + $formatter = new FlowdockFormatter('test_source', 'source@test.com'); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $formatted = $formatter->formatBatch($records); + + $this->assertArrayHasKey('flowdock', $formatted[0]); + $this->assertArrayHasKey('flowdock', $formatted[1]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php new file mode 100644 index 0000000000..622b2bae21 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/FluentdFormatterTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class FluentdFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\FluentdFormatter::__construct + * @covers Monolog\Formatter\FluentdFormatter::isUsingLevelsInTag + */ + public function testConstruct() + { + $formatter = new FluentdFormatter(); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(false); + $this->assertEquals(false, $formatter->isUsingLevelsInTag()); + $formatter = new FluentdFormatter(true); + $this->assertEquals(true, $formatter->isUsingLevelsInTag()); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormat() + { + $record = $this->getRecord(Logger::WARNING); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(); + $this->assertEquals( + '["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]', + $formatter->format($record) + ); + } + + /** + * @covers Monolog\Formatter\FluentdFormatter::format + */ + public function testFormatWithTag() + { + $record = $this->getRecord(Logger::ERROR); + $record['datetime'] = new \DateTime("@0"); + + $formatter = new FluentdFormatter(true); + $this->assertEquals( + '["test.error",0,{"message":"test","extra":[]}]', + $formatter->format($record) + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 0000000000..4a24761617 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('\Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php or mlehner/gelf-php is not installed"); + } + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals($this->isLegacy() ? 3 : 'error', $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + * @expectedException InvalidArgumentException + */ + public function testFormatInvalidFails() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + ); + + $formatter->format($record); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContextContainingException() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger', 'exception' => array( + 'class' => '\Exception', + 'file' => '/some/file/in/dir.php:56', + 'trace' => array('/some/file/1.php:23', '/some/file/2.php:3'), + )), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $this->assertEquals("/some/file/in/dir.php", $message->getFile()); + $this->assertEquals("56", $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } + + public function testFormatWithLargeData() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('exception' => str_repeat(' ', 32767)), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => str_repeat(' ', 32767)), + 'message' => 'log' + ); + $message = $formatter->format($record); + $messageArray = $message->toArray(); + + // 200 for padding + metadata + $length = 200; + + foreach ($messageArray as $key => $value) { + if (!in_array($key, array('level', 'timestamp'))) { + $length += strlen($value); + } + } + + $this->assertLessThanOrEqual(65792, $length, 'The message length is no longer than the maximum allowed length'); + } + + public function testFormatWithUnlimitedLength() + { + $formatter = new GelfMessageFormatter('LONG_SYSTEM_NAME', null, 'ctxt_', PHP_INT_MAX); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('exception' => str_repeat(' ', 32767 * 2)), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => str_repeat(' ', 32767 * 2)), + 'message' => 'log' + ); + $message = $formatter->format($record); + $messageArray = $message->toArray(); + + // 200 for padding + metadata + $length = 200; + + foreach ($messageArray as $key => $value) { + if (!in_array($key, array('level', 'timestamp'))) { + $length += strlen($value); + } + } + + $this->assertGreaterThanOrEqual(131289, $length, 'The message should not be truncated'); + } + + private function isLegacy() + { + return interface_exists('\Gelf\IMessagePublisher'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000000..c9445f363b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class JsonFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\JsonFormatter::__construct + * @covers Monolog\Formatter\JsonFormatter::getBatchMode + * @covers Monolog\Formatter\JsonFormatter::isAppendingNewlines + */ + public function testConstruct() + { + $formatter = new JsonFormatter(); + $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + $this->assertEquals(true, $formatter->isAppendingNewlines()); + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES, false); + $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $this->assertEquals(false, $formatter->isAppendingNewlines()); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormat() + { + $formatter = new JsonFormatter(); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record)."\n", $formatter->format($record)); + + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record), $formatter->format($record)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchJson + */ + public function testFormatBatch() + { + $formatter = new JsonFormatter(); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $this->assertEquals(json_encode($records), $formatter->formatBatch($records)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + * @covers Monolog\Formatter\JsonFormatter::formatBatchNewlines + */ + public function testFormatBatchNewlines() + { + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES); + $records = $expected = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + array_walk($expected, function (&$value, $key) { + $value = json_encode($value); + }); + $this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records)); + } + + public function testDefFormatWithException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo'); + $formattedException = $this->formatException($exception); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new JsonFormatter(); + $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?')); + $formattedPrevException = $this->formatException($exception->getPrevious()); + $formattedException = $this->formatException($exception, $formattedPrevException); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithThrowable() + { + if (!class_exists('Error') || !is_subclass_of('Error', 'Throwable')) { + $this->markTestSkipped('Requires PHP >=7'); + } + + $formatter = new JsonFormatter(); + $throwable = new \Error('Foo'); + $formattedThrowable = $this->formatException($throwable); + + $message = $this->formatRecordWithExceptionInContext($formatter, $throwable); + + $this->assertContextContainsFormattedException($formattedThrowable, $message); + } + + /** + * @param string $expected + * @param string $actual + * + * @internal param string $exception + */ + private function assertContextContainsFormattedException($expected, $actual) + { + $this->assertEquals( + '{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n", + $actual + ); + } + + /** + * @param JsonFormatter $formatter + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception) + { + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => $exception), + 'datetime' => null, + 'extra' => array(), + 'message' => 'foobar', + )); + return $message; + } + + /** + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatExceptionFilePathWithLine($exception) + { + $options = 0; + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + $path = substr(json_encode($exception->getFile(), $options), 1, -1); + return $path . ':' . $exception->getLine(); + } + + /** + * @param \Exception|\Throwable $exception + * + * @param null|string $previous + * + * @return string + */ + private function formatException($exception, $previous = null) + { + $formattedException = + '{"class":"' . get_class($exception) . + '","message":"' . $exception->getMessage() . + '","code":' . $exception->getCode() . + ',"file":"' . $this->formatExceptionFilePathWithLine($exception) . + ($previous ? '","previous":' . $previous : '"') . + '}'; + return $formattedException; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php new file mode 100644 index 0000000000..310d93cadb --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\LineFormatter + */ +class LineFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testDefFormatWithString() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'context' => array(), + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + )); + $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testDefFormatWithArrayContext() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'bool' => false, + 'null' => null, + ), + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux","bool":false,"null":null} []'."\n", $message); + } + + public function testDefFormatExtras() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testFormatExtras() + { + $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testContextAndExtraOptionallyNotShownIfEmpty() + { + $formatter = new LineFormatter(null, 'Y-m-d', false, true); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message); + } + + public function testContextAndExtraReplacement() + { + $formatter = new LineFormatter('%context.foo% => %extra.foo%'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 'bar'), + 'datetime' => new \DateTime, + 'extra' => array('foo' => 'xbar'), + 'message' => 'log', + )); + $this->assertEquals('bar => xbar', $message); + } + + public function testDefFormatWithObject() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'message' => 'foobar', + )); + + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: bar)","baz":[],"res":"[resource] (stream)"}'."\n", $message); + } + + public function testDefFormatWithException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo')), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).')"} []'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $previous = new \LogicException('Wut?'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).', LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message); + } + + public function testBatchFormat() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testFormatShouldStripInlineLineBreaks() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo bar/', $message); + } + + public function testFormatShouldNotStripInlineLineBreaksWhenFlagIsSet() + { + $formatter = new LineFormatter(null, 'Y-m-d', true); + $message = $formatter->format( + array( + 'message' => "foo\nbar", + 'context' => array(), + 'extra' => array(), + ) + ); + + $this->assertRegExp('/foo\nbar/', $message); + } +} + +class TestFoo +{ + public $foo = 'foo'; +} + +class TestBar +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php new file mode 100644 index 0000000000..6d59b3f3d1 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LogglyFormatterTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\TestCase; + +class LogglyFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\LogglyFormatter::__construct + */ + public function testConstruct() + { + $formatter = new LogglyFormatter(); + $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode()); + $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON); + $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode()); + } + + /** + * @covers Monolog\Formatter\LogglyFormatter::format + */ + public function testFormat() + { + $formatter = new LogglyFormatter(); + $record = $this->getRecord(); + $formatted_decoded = json_decode($formatter->format($record), true); + $this->assertArrayHasKey("timestamp", $formatted_decoded); + $this->assertEquals(new \DateTime($formatted_decoded["timestamp"]), $record["datetime"]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php new file mode 100644 index 0000000000..9f6b1cc438 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php @@ -0,0 +1,333 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('meh', $message['@fields']['channel']); + $this->assertContains('meh', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + + $formatter = new LogstashFormatter('mysystem'); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['@fields']['file']); + $this->assertEquals(14, $message['@fields']['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('key', $message_array); + $this->assertEquals('pair', $message_array['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('EXTkey', $message_array); + $this->assertEquals('pair', $message_array['EXTkey']); + } + + public function testFormatWithApplicationName() + { + $formatter = new LogstashFormatter('app', 'test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('@type', $message); + $this->assertEquals('app', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatterV1() + { + $formatter = new LogstashFormatter('test', 'hostname', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals("1", $message['@version']); + $this->assertEquals('log', $message['message']); + $this->assertEquals('meh', $message['channel']); + $this->assertEquals('ERROR', $message['level']); + $this->assertEquals('test', $message['type']); + $this->assertEquals('hostname', $message['host']); + + $formatter = new LogstashFormatter('mysystem', null, null, 'ctxt_', LogstashFormatter::V1); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLineV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['file']); + $this->assertEquals(14, $message['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContextV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('ctxt_from', $message); + $this->assertEquals('logger', $message['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('CTXfrom', $message); + $this->assertEquals('logger', $message['CTXfrom']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtraV1() + { + $formatter = new LogstashFormatter('test', null, null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('key', $message); + $this->assertEquals('pair', $message['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT', 'ctxt_', LogstashFormatter::V1); + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('EXTkey', $message); + $this->assertEquals('pair', $message['EXTkey']); + } + + public function testFormatWithApplicationNameV1() + { + $formatter = new LogstashFormatter('app', 'test', null, 'ctxt_', LogstashFormatter::V1); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('type', $message); + $this->assertEquals('app', $message['type']); + } + + public function testFormatWithLatin9Data() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => '¯\_(ツ)_/¯', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array( + 'user_agent' => "\xD6WN; FBCR/OrangeEspa\xF1a; Vers\xE3o/4.0; F\xE4rist", + ), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('¯\_(ツ)_/¯', $message['@fields']['channel']); + $this->assertContains('¯\_(ツ)_/¯', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertEquals('ÖWN; FBCR/OrangeEspaña; Versão/4.0; Färist', $message['@fields']['user_agent']); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertEquals(null, $message['@fields']['user_agent']); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php new file mode 100644 index 0000000000..52e699e025 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/MongoDBFormatterTest.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * @author Florian Plattner + */ +class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists('MongoDate')) { + $this->markTestSkipped('mongo extension not installed'); + } + } + + public function constructArgumentProvider() + { + return array( + array(1, true, 1, true), + array(0, false, 0, false), + ); + } + + /** + * @param $traceDepth + * @param $traceAsString + * @param $expectedTraceDepth + * @param $expectedTraceAsString + * + * @dataProvider constructArgumentProvider + */ + public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString) + { + $formatter = new MongoDBFormatter($traceDepth, $traceAsString); + + $reflTrace = new \ReflectionProperty($formatter, 'exceptionTraceAsString'); + $reflTrace->setAccessible(true); + $this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter)); + + $reflDepth = new\ReflectionProperty($formatter, 'maxNestingLevel'); + $reflDepth->setAccessible(true); + $this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter)); + } + + public function testSimpleFormat() + { + $record = array( + 'message' => 'some log message', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(7, $formattedRecord); + $this->assertEquals('some log message', $formattedRecord['message']); + $this->assertEquals(array(), $formattedRecord['context']); + $this->assertEquals(Logger::WARNING, $formattedRecord['level']); + $this->assertEquals(Logger::getLevelName(Logger::WARNING), $formattedRecord['level_name']); + $this->assertEquals('test', $formattedRecord['channel']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['datetime']); + $this->assertEquals('0.00000000 1391212800', $formattedRecord['datetime']->__toString()); + $this->assertEquals(array(), $formattedRecord['extra']); + } + + public function testRecursiveFormat() + { + $someObject = new \stdClass(); + $someObject->foo = 'something'; + $someObject->bar = 'stuff'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'stuff' => new \DateTime('2014-02-01 02:31:33'), + 'some_object' => $someObject, + 'context_string' => 'some string', + 'context_int' => 123456, + 'except' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(); + $formattedRecord = $formatter->format($record); + + $this->assertCount(5, $formattedRecord['context']); + $this->assertInstanceOf('\MongoDate', $formattedRecord['context']['stuff']); + $this->assertEquals('0.00000000 1391221893', $formattedRecord['context']['stuff']->__toString()); + $this->assertEquals( + array( + 'foo' => 'something', + 'bar' => 'stuff', + 'class' => 'stdClass', + ), + $formattedRecord['context']['some_object'] + ); + $this->assertEquals('some string', $formattedRecord['context']['context_string']); + $this->assertEquals(123456, $formattedRecord['context']['context_int']); + + $this->assertCount(5, $formattedRecord['context']['except']); + $this->assertEquals('exception message', $formattedRecord['context']['except']['message']); + $this->assertEquals(987, $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['file']); + $this->assertInternalType('integer', $formattedRecord['context']['except']['code']); + $this->assertInternalType('string', $formattedRecord['context']['except']['trace']); + $this->assertEquals('Exception', $formattedRecord['context']['except']['class']); + } + + public function testFormatDepthArray() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => array( + 'nest4' => 'value', + 'property' => 'nothing', + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthArrayInfiniteNesting() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(0); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'something', + 'nest3' => array( + 'property' => 'anything', + 'nest4' => array( + 'property' => 'nothing', + ), + ), + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthObjects() + { + $someObject = new \stdClass(); + $someObject->property = 'anything'; + $someObject->nest3 = new \stdClass(); + $someObject->nest3->property = 'nothing'; + $someObject->nest3->nest4 = 'invisible'; + + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => $someObject, + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, true); + $formattedResult = $formatter->format($record); + + $this->assertEquals( + array( + 'nest2' => array( + 'property' => 'anything', + 'nest3' => '[...]', + 'class' => 'stdClass', + ), + ), + $formattedResult['context'] + ); + } + + public function testFormatDepthException() + { + $record = array( + 'message' => 'some log message', + 'context' => array( + 'nest2' => new \Exception('exception message', 987), + ), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'test', + 'datetime' => new \DateTime('2014-02-01 00:00:00'), + 'extra' => array(), + ); + + $formatter = new MongoDBFormatter(2, false); + $formattedRecord = $formatter->format($record); + + $this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']); + $this->assertEquals(987, $formattedRecord['context']['nest2']['code']); + $this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 0000000000..57bcdf9849 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,423 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + \PHPUnit_Framework_Error_Warning::$enabled = true; + + return parent::tearDown(); + } + + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => INF, + '-inf' => -INF, + 'nan' => acos(4), + ), + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)', + 'baz' => array(), + 'res' => '[resource] (stream)', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + 'inf' => 'INF', + '-inf' => '-INF', + 'nan' => 'NaN', + ), + ), $formatted); + } + + public function testFormatExceptions() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \LogicException('bar'); + $e2 = new \RuntimeException('foo', 0, $e); + $formatted = $formatter->format(array( + 'exception' => $e2, + )); + + $this->assertGreaterThan(5, count($formatted['exception']['trace'])); + $this->assertTrue(isset($formatted['exception']['previous'])); + unset($formatted['exception']['trace'], $formatted['exception']['previous']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => get_class($e2), + 'message' => $e2->getMessage(), + 'code' => $e2->getCode(), + 'file' => $e2->getFile().':'.$e2->getLine(), + ), + ), $formatted); + } + + public function testFormatSoapFaultException() + { + if (!class_exists('SoapFault')) { + $this->markTestSkipped('Requires the soap extension'); + } + + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \SoapFault('foo', 'bar', 'hello', 'world'); + $formatted = $formatter->format(array( + 'exception' => $e, + )); + + unset($formatted['exception']['trace']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => 'SoapFault', + 'message' => 'bar', + 'code' => 0, + 'file' => $e->getFile().':'.$e->getLine(), + 'faultcode' => 'foo', + 'faultactor' => 'hello', + 'detail' => 'world', + ), + ), $formatted); + } + + public function testFormatToStringExceptionHandle() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $this->setExpectedException('RuntimeException', 'Could not convert to string'); + $formatter->format(array( + 'myObject' => new TestToStringError(), + )); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } + + /** + * Test issue #137 + */ + public function testIgnoresRecursiveObjectReferences() + { + // set up the recursion + $foo = new \stdClass(); + $bar = new \stdClass(); + + $foo->bar = $bar; + $bar->foo = $foo; + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($foo, $bar), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($foo, $bar)), $res); + } + + public function testIgnoresInvalidTypes() + { + // set up the recursion + $resource = fopen(__FILE__, 'r'); + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($resource), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($resource)), $res); + } + + public function testNormalizeHandleLargeArrays() + { + $formatter = new NormalizerFormatter(); + $largeArray = range(1, 2000); + + $res = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array($largeArray), + 'datetime' => new \DateTime, + 'extra' => array(), + )); + + $this->assertCount(1000, $res['context'][0]); + $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']); + } + + /** + * @expectedException RuntimeException + */ + public function testThrowsOnInvalidEncoding() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + // send an invalid unicode sequence as a object that can't be cleaned + $record = new \stdClass; + $record->message = "\xB1\x31"; + $res = $reflMethod->invoke($formatter, $record); + if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') { + throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely'); + } + } + + public function testConvertsInvalidEncodingAsLatin9() + { + if (version_compare(PHP_VERSION, '5.5.0', '<')) { + // Ignore the warning that will be emitted by PHP <5.5.0 + \PHPUnit_Framework_Error_Warning::$enabled = false; + } + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + + $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE")); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res); + } else { + // PHP <5.5 does not return false for an element encoding failure, + // instead it emits a warning (possibly) and nulls the value. + $this->assertSame('{"message":null}', $res); + } + } + + /** + * @param mixed $in Input + * @param mixed $expect Expected output + * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8 + * @dataProvider providesDetectAndCleanUtf8 + */ + public function testDetectAndCleanUtf8($in, $expect) + { + $formatter = new NormalizerFormatter(); + $formatter->detectAndCleanUtf8($in); + $this->assertSame($expect, $in); + } + + public function providesDetectAndCleanUtf8() + { + $obj = new \stdClass; + + return array( + 'null' => array(null, null), + 'int' => array(123, 123), + 'float' => array(123.45, 123.45), + 'bool false' => array(false, false), + 'bool true' => array(true, true), + 'ascii string' => array('abcdef', 'abcdef'), + 'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'), + 'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'), + 'empty array' => array(array(), array()), + 'array' => array(array('abcdef'), array('abcdef')), + 'object' => array($obj, $obj), + ); + } + + /** + * @param int $code + * @param string $msg + * @dataProvider providesHandleJsonErrorFailure + */ + public function testHandleJsonErrorFailure($code, $msg) + { + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError'); + $reflMethod->setAccessible(true); + + $this->setExpectedException('RuntimeException', $msg); + $reflMethod->invoke($formatter, $code, 'faked'); + } + + public function providesHandleJsonErrorFailure() + { + return array( + 'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'), + 'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'), + 'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'), + 'default' => array(-1, 'Unknown error'), + ); + } + + public function testExceptionTraceWithArgs() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported in HHVM since it detects errors differently'); + } + + // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered + // and no file or line are included in the trace because it's treated as internal function + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); + }); + + try { + // This will contain $resource and $wrappedResource as arguments in the trace item + $resource = fopen('php://memory', 'rw+'); + fwrite($resource, 'test_resource'); + $wrappedResource = new TestFooNorm; + $wrappedResource->foo = $resource; + // Just do something stupid with a resource/wrapped resource as argument + array_keys($wrappedResource); + } catch (\Exception $e) { + restore_error_handler(); + } + + $formatter = new NormalizerFormatter(); + $record = array('context' => array('exception' => $e)); + $result = $formatter->format($record); + + $this->assertRegExp( + '%"resource":"\[resource\] \(stream\)"%', + $result['context']['exception']['trace'][0] + ); + + if (version_compare(PHP_VERSION, '5.5.0', '>=')) { + $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%'; + } else { + $pattern = '%\\\\"foo\\\\":null%'; + } + + // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4 + $this->assertRegExp( + $pattern, + $result['context']['exception']['trace'][0] + ); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +} + +class TestStreamFoo +{ + public $foo; + public $resource; + + public function __construct($resource) + { + $this->resource = $resource; + $this->foo = 'BAR'; + } + + public function __toString() + { + fseek($this->resource, 0); + + return $this->foo . ' - ' . (string) stream_get_contents($this->resource); + } +} + +class TestToStringError +{ + public function __toString() + { + throw new \RuntimeException('Could not convert to string'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php new file mode 100644 index 0000000000..b1c8fd491f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ScalarFormatterTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +class ScalarFormatterTest extends \PHPUnit_Framework_TestCase +{ + private $formatter; + + public function setUp() + { + $this->formatter = new ScalarFormatter(); + } + + public function buildTrace(\Exception $e) + { + $data = array(); + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data[] = $frame['file'].':'.$frame['line']; + } else { + $data[] = json_encode($frame); + } + } + + return $data; + } + + public function encodeJson($data) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } + + public function testFormat() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => array(1, 2, 3), + 'bat' => array('foo' => 'bar'), + 'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'), + 'ban' => $exception, + )); + + $this->assertSame(array( + 'foo' => 'string', + 'bar' => 1, + 'baz' => false, + 'bam' => $this->encodeJson(array(1, 2, 3)), + 'bat' => $this->encodeJson(array('foo' => 'bar')), + 'bap' => '1970-01-01 00:00:00', + 'ban' => $this->encodeJson(array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + )), + ), $formatted); + } + + public function testFormatWithErrorContext() + { + $context = array('file' => 'foo', 'line' => 1); + $formatted = $this->formatter->format(array( + 'context' => $context, + )); + + $this->assertSame(array( + 'context' => $this->encodeJson($context), + ), $formatted); + } + + public function testFormatWithExceptionContext() + { + $exception = new \Exception('foo'); + $formatted = $this->formatter->format(array( + 'context' => array( + 'exception' => $exception, + ), + )); + + $this->assertSame(array( + 'context' => $this->encodeJson(array( + 'exception' => array( + 'class' => get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + 'trace' => $this->buildTrace($exception), + ), + )), + ), $formatted); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 0000000000..52f15a3607 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testDefaultFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithFileAndLine() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithoutContext() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::formatBatch + * @expectedException BadMethodCallException + */ + public function testBatchFormatThrowException() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $wildfire->formatBatch(array($record)); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testTableFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'table-channel', + 'context' => array( + WildfireFormatter::TABLE => array( + array('col1', 'col2', 'col3'), + array('val1', 'val2', 'val3'), + array('foo1', 'foo2', 'foo3'), + array('bar1', 'bar2', 'bar3'), + ), + ), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'table-message', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '171|[{"Type":"TABLE","File":"","Line":"","Label":"table-channel: table-message"},[["col1","col2","col3"],["val1","val2","val3"],["foo1","foo2","foo3"],["bar1","bar2","bar3"]]]|', + $message + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000000..568eb9dad5 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\WebProcessor; + +class AbstractHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractHandler::__construct + * @covers Monolog\Handler\AbstractHandler::getLevel + * @covers Monolog\Handler\AbstractHandler::setLevel + * @covers Monolog\Handler\AbstractHandler::getBubble + * @covers Monolog\Handler\AbstractHandler::setBubble + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertEquals(Logger::WARNING, $handler->getLevel()); + $this->assertEquals(false, $handler->getBubble()); + + $handler->setLevel(Logger::ERROR); + $handler->setBubble(true); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertEquals(Logger::ERROR, $handler->getLevel()); + $this->assertEquals(true, $handler->getBubble()); + $this->assertSame($formatter, $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::handleBatch + */ + public function testHandleBatch() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $handler->expects($this->exactly(2)) + ->method('handle'); + $handler->handleBatch(array($this->getRecord(), $this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractHandler::isHandling + */ + public function testIsHandling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->isHandling($this->getRecord())); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::__construct + */ + public function testHandlesPsrStyleLevels() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array('warning', false)); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $handler->setLevel('debug'); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @covers Monolog\Handler\AbstractHandler::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + + $handler->pushProcessor(new \stdClass()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php new file mode 100644 index 0000000000..24d4f63ce3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Processor\WebProcessor; + +class AbstractProcessingHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleLowerLevelMessage() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true)); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true)); + $this->assertFalse($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleNotBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false)); + $this->assertTrue($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleIsFalseWhenNotHandled() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->handle($this->getRecord())); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::processRecord + */ + public function testProcessRecord() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $handler->pushProcessor(new WebProcessor(array( + 'REQUEST_URI' => '', + 'REQUEST_METHOD' => '', + 'REMOTE_ADDR' => '', + 'SERVER_NAME' => '', + 'UNIQUE_ID' => '', + ))); + $handledRecord = null; + $handler->expects($this->once()) + ->method('write') + ->will($this->returnCallback(function ($record) use (&$handledRecord) { + $handledRecord = $record; + })) + ; + $handler->handle($this->getRecord()); + $this->assertEquals(6, count($handledRecord['extra'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php new file mode 100644 index 0000000000..8e0e7237b4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Connection\AMQPConnection; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class AmqpHandlerTest extends TestCase +{ + public function testHandleAmqpExt() + { + if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) { + $this->markTestSkipped("amqp-php not installed"); + } + + if (!class_exists('AMQPChannel')) { + $this->markTestSkipped("Please update AMQP to version >= 1.0"); + } + + $messages = array(); + + $exchange = $this->getMock('AMQPExchange', array('publish', 'setName'), array(), '', false); + $exchange->expects($this->once()) + ->method('setName') + ->with('log') + ; + $exchange->expects($this->any()) + ->method('publish') + ->will($this->returnCallback(function ($message, $routing_key, $flags = 0, $attributes = array()) use (&$messages) { + $messages[] = array($message, $routing_key, $flags, $attributes); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'warn.test', + 0, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + $messages[0][0] = json_decode($messages[0][0], true); + unset($messages[0][0]['datetime']); + $this->assertEquals($expected, $messages[0]); + } + + public function testHandlePhpAmqpLib() + { + if (!class_exists('PhpAmqpLib\Connection\AMQPConnection')) { + $this->markTestSkipped("php-amqplib not installed"); + } + + $messages = array(); + + $exchange = $this->getMock('PhpAmqpLib\Channel\AMQPChannel', array('basic_publish', '__destruct'), array(), '', false); + + $exchange->expects($this->any()) + ->method('basic_publish') + ->will($this->returnCallback(function (AMQPMessage $msg, $exchange = "", $routing_key = "", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) { + $messages[] = array($msg, $exchange, $routing_key, $mandatory, $immediate, $ticket); + })) + ; + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'log', + 'warn.test', + false, + false, + null, + array( + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ), + ); + + $handler->handle($record); + + $this->assertCount(1, $messages); + + /* @var $msg AMQPMessage */ + $msg = $messages[0][0]; + $messages[0][0] = json_decode($msg->body, true); + $messages[0][] = $msg->get_properties(); + unset($messages[0][0]['datetime']); + + $this->assertEquals($expected, $messages[0]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php new file mode 100644 index 0000000000..ffb1d746af --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/BrowserConsoleHandlerTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\BrowserConsoleHandlerTest + */ +class BrowserConsoleHandlerTest extends TestCase +{ + protected function setUp() + { + BrowserConsoleHandler::reset(); + } + + protected function generateScript() + { + $reflMethod = new \ReflectionMethod('Monolog\Handler\BrowserConsoleHandler', 'generateScript'); + $reflMethod->setAccessible(true); + + return $reflMethod->invoke(null); + } + + public function testStyling() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'foo[[bar]]{color: red}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testEscaping() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, "[foo] [[\"bar\n[baz]\"]]{color: red}")); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testAutolabel() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[bar]]{macro: autolabel}')); + $handler->handle($this->getRecord(Logger::DEBUG, '[[foo]]{macro: autolabel}')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testContext() + { + $handler = new BrowserConsoleHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + + $handler->handle($this->getRecord(Logger::DEBUG, 'test', array('foo' => 'bar'))); + + $expected = <<assertEquals($expected, $this->generateScript()); + } + + public function testConcurrentHandlers() + { + $handler1 = new BrowserConsoleHandler(); + $handler1->setFormatter($this->getIdentityFormatter()); + + $handler2 = new BrowserConsoleHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + + $handler1->handle($this->getRecord(Logger::DEBUG, 'test1')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test2')); + $handler1->handle($this->getRecord(Logger::DEBUG, 'test3')); + $handler2->handle($this->getRecord(Logger::DEBUG, 'test4')); + + $expected = <<assertEquals($expected, $this->generateScript()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php new file mode 100644 index 0000000000..da8b3c3922 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class BufferHandlerTest extends TestCase +{ + private $shutdownCheckHandler; + + /** + * @covers Monolog\Handler\BufferHandler::__construct + * @covers Monolog\Handler\BufferHandler::handle + * @covers Monolog\Handler\BufferHandler::close + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + + /** + * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testPropagatesRecordsAtEndOfRequest() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->shutdownCheckHandler = $test; + register_shutdown_function(array($this, 'checkPropagation')); + } + + public function checkPropagation() + { + if (!$this->shutdownCheckHandler->hasWarningRecords() || !$this->shutdownCheckHandler->hasDebugRecords()) { + echo '!!! BufferHandlerTest::testPropagatesRecordsAtEndOfRequest failed to verify that the messages have been propagated' . PHP_EOL; + exit(1); + } + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleLevel() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->flush(); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php new file mode 100644 index 0000000000..0449f8b1a3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\ChromePHPHandler + */ +class ChromePHPHandlerTest extends TestCase +{ + protected function setUp() + { + TestChromePHPHandler::reset(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0'; + } + + /** + * @dataProvider agentsProvider + */ + public function testHeaders($agent) + { + $_SERVER['HTTP_USER_AGENT'] = $agent; + + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public static function agentsProvider() + { + return array( + array('Monolog Test; Chrome/1.0'), + array('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'), + array('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/56.0.2924.76 Chrome/56.0.2924.76 Safari/537.36'), + array('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome Safari/537.36'), + ); + } + + public function testHeadersOverflow() + { + $handler = new TestChromePHPHandler(); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150 * 1024))); + + // overflow chrome headers limit + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100 * 1024))); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + array( + 'test', + 'test', + 'unknown', + 'log', + ), + array( + 'test', + str_repeat('a', 150 * 1024), + 'unknown', + 'warn', + ), + array( + 'monolog', + 'Incomplete logs, chrome header size limit reached', + 'unknown', + 'warn', + ), + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestChromePHPHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + 'test', + 'test', + ), + 'request_uri' => '', + )))), + ); + + $this->assertEquals($expected, $handler2->getHeaders()); + } +} + +class TestChromePHPHandler extends ChromePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$overflowed = false; + self::$sendHeaders = true; + self::$json['rows'] = array(); + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php new file mode 100644 index 0000000000..9fc4b38850 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class CouchDBHandlerTest extends TestCase +{ + public function testHandle() + { + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new CouchDBHandler(); + + try { + $handler->handle($record); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984'); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php new file mode 100644 index 0000000000..e2aff868a1 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DeduplicationHandlerTest.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DeduplicationHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + */ + public function testFlushPassthruIfAllRecordsUnderTrigger() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + + $handler->flush(); + + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + */ + public function testFlushPassthruIfEmptyLog() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushSkipsIfLogExists() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar')); + $handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar")); + + $handler->flush(); + + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @depends testFlushPassthruIfEmptyLog + */ + public function testFlushPassthruIfLogTooOld() + { + $test = new TestHandler(); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + $record = $this->getRecord(Logger::CRITICAL); + $record['datetime']->modify('+62seconds'); + $handler->handle($record); + + $handler->flush(); + + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\DeduplicationHandler::flush + * @covers Monolog\Handler\DeduplicationHandler::appendRecord + * @covers Monolog\Handler\DeduplicationHandler::isDuplicate + * @covers Monolog\Handler\DeduplicationHandler::collectLogs + */ + public function testGcOldLogs() + { + $test = new TestHandler(); + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0); + + // handle two records from yesterday, and one recent + $record = $this->getRecord(Logger::ERROR); + $record['datetime']->modify('-1day -10seconds'); + $handler->handle($record); + $record2 = $this->getRecord(Logger::CRITICAL); + $record2['datetime']->modify('-1day -10seconds'); + $handler->handle($record2); + $record3 = $this->getRecord(Logger::CRITICAL); + $record3['datetime']->modify('-30seconds'); + $handler->handle($record3); + + // log is written as none of them are duplicate + $handler->flush(); + $this->assertSame( + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + + // clear test handler + $test->clear(); + $this->assertFalse($test->hasErrorRecords()); + $this->assertFalse($test->hasCriticalRecords()); + + // log new records, duplicate log gets GC'd at the end of this flush call + $handler->handle($record = $this->getRecord(Logger::ERROR)); + $handler->handle($record2 = $this->getRecord(Logger::CRITICAL)); + $handler->flush(); + + // log should now contain the new errors and the previous one that was recent enough + $this->assertSame( + $record3['datetime']->getTimestamp() . ":CRITICAL:test\n" . + $record['datetime']->getTimestamp() . ":ERROR:test\n" . + $record2['datetime']->getTimestamp() . ":CRITICAL:test\n", + file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log') + ); + $this->assertTrue($test->hasErrorRecords()); + $this->assertTrue($test->hasCriticalRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + public static function tearDownAfterClass() + { + @unlink(sys_get_temp_dir().'/monolog_dedup.log'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php new file mode 100644 index 0000000000..d67da90aec --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DoctrineCouchDBHandlerTest extends TestCase +{ + protected function setup() + { + if (!class_exists('Doctrine\CouchDB\CouchDBClient')) { + $this->markTestSkipped('The "doctrine/couchdb" package is not installed'); + } + } + + public function testHandle() + { + $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient') + ->setMethods(array('postDocument')) + ->disableOriginalConstructor() + ->getMock(); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $client->expects($this->once()) + ->method('postDocument') + ->with($expected); + + $handler = new DoctrineCouchDBHandler($client); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php new file mode 100644 index 0000000000..2e6c348d1a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DynamoDbHandlerTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class DynamoDbHandlerTest extends TestCase +{ + private $client; + + public function setUp() + { + if (!class_exists('Aws\DynamoDb\DynamoDbClient')) { + $this->markTestSkipped('aws/aws-sdk-php not installed'); + } + + $this->client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient') + ->setMethods(array('formatAttributes', '__call')) + ->disableOriginalConstructor()->getMock(); + } + + public function testConstruct() + { + $this->assertInstanceOf('Monolog\Handler\DynamoDbHandler', new DynamoDbHandler($this->client, 'foo')); + } + + public function testInterface() + { + $this->assertInstanceOf('Monolog\Handler\HandlerInterface', new DynamoDbHandler($this->client, 'foo')); + } + + public function testGetFormatter() + { + $handler = new DynamoDbHandler($this->client, 'foo'); + $this->assertInstanceOf('Monolog\Formatter\ScalarFormatter', $handler->getFormatter()); + } + + public function testHandle() + { + $record = $this->getRecord(); + $formatter = $this->getMock('Monolog\Formatter\FormatterInterface'); + $formatted = array('foo' => 1, 'bar' => 2); + $handler = new DynamoDbHandler($this->client, 'foo'); + $handler->setFormatter($formatter); + + $isV3 = defined('Aws\Sdk::VERSION') && version_compare(\Aws\Sdk::VERSION, '3.0', '>='); + if ($isV3) { + $expFormatted = array('foo' => array('N' => 1), 'bar' => array('N' => 2)); + } else { + $expFormatted = $formatted; + } + + $formatter + ->expects($this->once()) + ->method('format') + ->with($record) + ->will($this->returnValue($formatted)); + $this->client + ->expects($isV3 ? $this->never() : $this->once()) + ->method('formatAttributes') + ->with($this->isType('array')) + ->will($this->returnValue($formatted)); + $this->client + ->expects($this->once()) + ->method('__call') + ->with('putItem', array(array( + 'TableName' => 'foo', + 'Item' => $expFormatted, + ))); + + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php new file mode 100644 index 0000000000..1687074b88 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ElasticSearchHandlerTest.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\TestCase; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Request; +use Elastica\Response; + +class ElasticSearchHandlerTest extends TestCase +{ + /** + * @var Client mock + */ + protected $client; + + /** + * @var array Default handler options + */ + protected $options = array( + 'index' => 'my_index', + 'type' => 'doc_type', + ); + + public function setUp() + { + // Elastica lib required + if (!class_exists("Elastica\Client")) { + $this->markTestSkipped("ruflin/elastica not installed"); + } + + // base mock Elastica Client object + $this->client = $this->getMockBuilder('Elastica\Client') + ->setMethods(array('addDocuments')) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::write + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandle() + { + // log message + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + // format expected result + $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']); + $expected = array($formatter->format($msg)); + + // setup ES client mock + $this->client->expects($this->any()) + ->method('addDocuments') + ->with($expected); + + // perform tests + $handler = new ElasticSearchHandler($this->client, $this->options); + $handler->handle($msg); + $handler->handleBatch(array($msg)); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + */ + public function testSetFormatter() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new ElasticaFormatter('index_new', 'type_new'); + $handler->setFormatter($formatter); + $this->assertInstanceOf('Monolog\Formatter\ElasticaFormatter', $handler->getFormatter()); + $this->assertEquals('index_new', $handler->getFormatter()->getIndex()); + $this->assertEquals('type_new', $handler->getFormatter()->getType()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::setFormatter + * @expectedException InvalidArgumentException + * @expectedExceptionMessage ElasticSearchHandler is only compatible with ElasticaFormatter + */ + public function testSetFormatterInvalid() + { + $handler = new ElasticSearchHandler($this->client); + $formatter = new NormalizerFormatter(); + $handler->setFormatter($formatter); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::getOptions + */ + public function testOptions() + { + $expected = array( + 'index' => $this->options['index'], + 'type' => $this->options['type'], + 'ignore_error' => false, + ); + $handler = new ElasticSearchHandler($this->client, $this->options); + $this->assertEquals($expected, $handler->getOptions()); + } + + /** + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @dataProvider providerTestConnectionErrors + */ + public function testConnectionErrors($ignore, $expectedError) + { + $clientOpts = array('host' => '127.0.0.1', 'port' => 1); + $client = new Client($clientOpts); + $handlerOpts = array('ignore_error' => $ignore); + $handler = new ElasticSearchHandler($client, $handlerOpts); + + if ($expectedError) { + $this->setExpectedException($expectedError[0], $expectedError[1]); + $handler->handle($this->getRecord()); + } else { + $this->assertFalse($handler->handle($this->getRecord())); + } + } + + /** + * @return array + */ + public function providerTestConnectionErrors() + { + return array( + array(false, array('RuntimeException', 'Error sending messages to Elasticsearch')), + array(true, false), + ); + } + + /** + * Integration test using localhost Elastic Search server + * + * @covers Monolog\Handler\ElasticSearchHandler::__construct + * @covers Monolog\Handler\ElasticSearchHandler::handleBatch + * @covers Monolog\Handler\ElasticSearchHandler::bulkSend + * @covers Monolog\Handler\ElasticSearchHandler::getDefaultFormatter + */ + public function testHandleIntegration() + { + $msg = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('foo' => 7, 'bar', 'class' => new \stdClass), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $expected = $msg; + $expected['datetime'] = $msg['datetime']->format(\DateTime::ISO8601); + $expected['context'] = array( + 'class' => '[object] (stdClass: {})', + 'foo' => 7, + 0 => 'bar', + ); + + $client = new Client(); + $handler = new ElasticSearchHandler($client, $this->options); + try { + $handler->handleBatch(array($msg)); + } catch (\RuntimeException $e) { + $this->markTestSkipped("Cannot connect to Elastic Search server on localhost"); + } + + // check document id from ES server response + $documentId = $this->getCreatedDocId($client->getLastResponse()); + $this->assertNotEmpty($documentId, 'No elastic document id received'); + + // retrieve document source from ES and validate + $document = $this->getDocSourceFromElastic( + $client, + $this->options['index'], + $this->options['type'], + $documentId + ); + $this->assertEquals($expected, $document); + + // remove test index from ES + $client->request("/{$this->options['index']}", Request::DELETE); + } + + /** + * Return last created document id from ES response + * @param Response $response Elastica Response object + * @return string|null + */ + protected function getCreatedDocId(Response $response) + { + $data = $response->getData(); + if (!empty($data['items'][0]['create']['_id'])) { + return $data['items'][0]['create']['_id']; + } + } + + /** + * Retrieve document by id from Elasticsearch + * @param Client $client Elastica client + * @param string $index + * @param string $type + * @param string $documentId + * @return array + */ + protected function getDocSourceFromElastic(Client $client, $index, $type, $documentId) + { + $resp = $client->request("/{$index}/{$type}/{$documentId}", Request::GET); + $data = $resp->getData(); + if (!empty($data['_source'])) { + return $data['_source']; + } + + return array(); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php new file mode 100644 index 0000000000..99785cbbb2 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +function error_log() +{ + $GLOBALS['error_log'][] = func_get_args(); +} + +class ErrorLogHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['error_log'] = array(); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::__construct + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The given message type "42" is not supported + */ + public function testShouldNotAcceptAnInvalidTypeOnContructor() + { + new ErrorLogHandler(42); + } + + /** + * @covers Monolog\Handler\ErrorLogHandler::write + */ + public function testShouldLogMessagesUsingErrorLogFuncion() + { + $type = ErrorLogHandler::OPERATING_SYSTEM; + $handler = new ErrorLogHandler($type); + $handler->setFormatter(new LineFormatter('%channel%.%level_name%: %message% %context% %extra%', null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertSame("test.ERROR: Foo\nBar\r\n\r\nBaz [] []", $GLOBALS['error_log'][0][0]); + $this->assertSame($GLOBALS['error_log'][0][1], $type); + + $handler = new ErrorLogHandler($type, Logger::DEBUG, true, true); + $handler->setFormatter(new LineFormatter(null, null, true)); + $handler->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + + $this->assertStringMatchesFormat('[%s] test.ERROR: Foo', $GLOBALS['error_log'][1][0]); + $this->assertSame($GLOBALS['error_log'][1][1], $type); + + $this->assertStringMatchesFormat('Bar', $GLOBALS['error_log'][2][0]); + $this->assertSame($GLOBALS['error_log'][2][1], $type); + + $this->assertStringMatchesFormat('Baz [] []', $GLOBALS['error_log'][3][0]); + $this->assertSame($GLOBALS['error_log'][3][1], $type); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php new file mode 100644 index 0000000000..31b7686a09 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FilterHandlerTest.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class FilterHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testIsHandling() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::INFO))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::NOTICE))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::CRITICAL))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::ALERT))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::EMERGENCY))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::isHandling + */ + public function testHandleProcessOnlyNeededLevels() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertTrue($test->hasNoticeRecords()); + + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertFalse($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + $handler->handle($this->getRecord(Logger::ALERT)); + $this->assertFalse($test->hasAlertRecords()); + $handler->handle($this->getRecord(Logger::EMERGENCY)); + $this->assertFalse($test->hasEmergencyRecords()); + + $test = new TestHandler(); + $handler = new FilterHandler($test, array(Logger::INFO, Logger::ERROR)); + + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::NOTICE)); + $this->assertFalse($test->hasNoticeRecords()); + $handler->handle($this->getRecord(Logger::ERROR)); + $this->assertTrue($test->hasErrorRecords()); + $handler->handle($this->getRecord(Logger::CRITICAL)); + $this->assertFalse($test->hasCriticalRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::setAcceptedLevels + * @covers Monolog\Handler\FilterHandler::getAcceptedLevels + */ + public function testAcceptedLevelApi() + { + $test = new TestHandler(); + $handler = new FilterHandler($test); + + $levels = array(Logger::INFO, Logger::ERROR); + $handler->setAcceptedLevels($levels); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels(array('info', 'error')); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $levels = array(Logger::CRITICAL, Logger::ALERT, Logger::EMERGENCY); + $handler->setAcceptedLevels(Logger::CRITICAL, Logger::EMERGENCY); + $this->assertSame($levels, $handler->getAcceptedLevels()); + + $handler->setAcceptedLevels('critical', 'emergency'); + $this->assertSame($levels, $handler->getAcceptedLevels()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FilterHandler($test, Logger::DEBUG, Logger::EMERGENCY); + $handler->pushProcessor( + function ($record) { + $record['extra']['foo'] = true; + + return $record; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleRespectsBubble() + { + $test = new TestHandler(); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, false); + $this->assertTrue($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + + $handler = new FilterHandler($test, Logger::INFO, Logger::NOTICE, true); + $this->assertFalse($handler->handle($this->getRecord(Logger::INFO))); + $this->assertFalse($handler->handle($this->getRecord(Logger::WARNING))); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FilterHandler( + function ($record, $handler) use ($test) { + return $test; + }, Logger::INFO, Logger::NOTICE, false + ); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FilterHandler::handle + * @expectedException \RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FilterHandler( + function ($record, $handler) { + return 'foo'; + } + ); + $handler->handle($this->getRecord(Logger::WARNING)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php new file mode 100644 index 0000000000..b92bf437be --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy; +use Psr\Log\LogLevel; + +class FingersCrossedHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleStopsBufferingAfterTrigger() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @covers Monolog\Handler\FingersCrossedHandler::reset + */ + public function testHandleRestartBufferingAfterReset() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->reset(); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler(function ($record, $handler) use ($test) { + return $test; + }); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + * @expectedException RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FingersCrossedHandler(function ($record, $handler) { + return 'foo'; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::isHandling + */ + public function testIsHandlingAlways() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::ERROR); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategyWithPsrLevel() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testOverrideActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning')); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->activate(); + $this->assertTrue($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategyWithPsrLevels() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy('error', array('othertest' => 'debug'))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::activate + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::INFO); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::close + */ + public function testPsrLevelPassthruOnClose() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING), 0, true, true, LogLevel::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->close(); + $this->assertFalse($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000000..0eb10a63fc --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\FirePHPHandler + */ +class FirePHPHandlerTest extends TestCase +{ + public function setUp() + { + TestFirePHPHandler::reset(); + $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0'; + } + + public function testHeaders() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestFirePHPHandler; + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => 'test', + 'X-Wf-1-1-1-4' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); + } +} + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$sendHeaders = true; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php new file mode 100644 index 0000000000..91cdd31283 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FleepHookHandlerTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass \Monolog\Handler\FleepHookHandler + */ +class FleepHookHandlerTest extends TestCase +{ + /** + * Default token to use in tests + */ + const TOKEN = '123abc'; + + /** + * @var FleepHookHandler + */ + private $handler; + + public function setUp() + { + parent::setUp(); + + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl extension to run'); + } + + // Create instances of the handler and logger for convenience + $this->handler = new FleepHookHandler(self::TOKEN); + } + + /** + * @covers ::__construct + */ + public function testConstructorSetsExpectedDefaults() + { + $this->assertEquals(Logger::DEBUG, $this->handler->getLevel()); + $this->assertEquals(true, $this->handler->getBubble()); + } + + /** + * @covers ::getDefaultFormatter + */ + public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays() + { + $record = array( + 'message' => 'msg', + 'context' => array(), + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + + $expectedFormatter = new LineFormatter(null, null, true, true); + $expected = $expectedFormatter->format($record); + + $handlerFormatter = $this->handler->getFormatter(); + $actual = $handlerFormatter->format($record); + + $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered'); + } + + /** + * @covers ::__construct + */ + public function testConnectionStringisConstructedCorrectly() + { + $this->assertEquals('ssl://' . FleepHookHandler::FLEEP_HOST . ':443', $this->handler->getConnectionString()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php new file mode 100644 index 0000000000..4b120d51ac --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FlowdockHandlerTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FlowdockFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Dominik Liebler + * @see https://www.hipchat.com/docs/api + */ +class FlowdockHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var FlowdockHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/messages\/team_inbox\/.* HTTP\/1.1\\r\\nHost: api.flowdock.com\\r\\nContent-Type: application\/json\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/"source":"test_source"/', $content); + $this->assertRegexp('/"from_address":"source@test\.com"/', $content); + } + + private function createHandler($token = 'myToken') + { + $constructorArgs = array($token, Logger::DEBUG); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\FlowdockHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter(new FlowdockFormatter('test_source', 'source@test.com')); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php new file mode 100644 index 0000000000..9d007b13db --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerLegacyTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerLegacyTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\MessagePublisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + + require_once __DIR__ . '/GelfMockMessagePublisher.php'; + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return new GelfMockMessagePublisher('localhost'); + } + + public function testDebug() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testWarning() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testInjectedGelfMessageFormatter() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + $handler->handle($record); + + $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost()); + $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray()); + $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php new file mode 100644 index 0000000000..8cdd64f441 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\Message; +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Gelf\Publisher') || !class_exists('Gelf\Message')) { + $this->markTestSkipped("graylog2/gelf-php not installed"); + } + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return $this->getMock('Gelf\Publisher', array('publish'), array(), '', false); + } + + public function testDebug() + { + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(7) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testWarning() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + + $handler->handle($record); + } + + public function testInjectedGelfMessageFormatter() + { + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + + $expectedMessage = new Message(); + $expectedMessage + ->setLevel(4) + ->setFacility("test") + ->setHost("mysystem") + ->setShortMessage($record['message']) + ->setTimestamp($record['datetime']) + ->setAdditional("EXTblarg", 'yep') + ->setAdditional("CTXfrom", 'logger') + ; + + $messagePublisher = $this->getMessagePublisher(); + $messagePublisher->expects($this->once()) + ->method('publish') + ->with($expectedMessage); + + $handler = $this->getHandler($messagePublisher); + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php new file mode 100644 index 0000000000..873d92fb6f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMockMessagePublisher.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\MessagePublisher; +use Gelf\Message; + +class GelfMockMessagePublisher extends MessagePublisher +{ + public function publish(Message $message) + { + $this->lastMessage = $message; + } + + public $lastMessage = null; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php new file mode 100644 index 0000000000..a1b86176d0 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class GroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new GroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new GroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new GroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleBatchUsesProcessors() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php new file mode 100644 index 0000000000..d8d0452caa --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/HandlerWrapperTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @author Alexey Karapetov + */ +class HandlerWrapperTest extends TestCase +{ + /** + * @var HandlerWrapper + */ + private $wrapper; + + private $handler; + + public function setUp() + { + parent::setUp(); + $this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface'); + $this->wrapper = new HandlerWrapper($this->handler); + } + + /** + * @return array + */ + public function trueFalseDataProvider() + { + return array( + array(true), + array(false), + ); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testIsHandling($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('isHandling') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->isHandling($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandle($result) + { + $record = $this->getRecord(); + $this->handler->expects($this->once()) + ->method('handle') + ->with($record) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handle($record)); + } + + /** + * @param $result + * @dataProvider trueFalseDataProvider + */ + public function testHandleBatch($result) + { + $records = $this->getMultipleRecords(); + $this->handler->expects($this->once()) + ->method('handleBatch') + ->with($records) + ->willReturn($result); + + $this->assertEquals($result, $this->wrapper->handleBatch($records)); + } + + public function testPushProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('pushProcessor') + ->with($processor); + + $this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor)); + } + + public function testPopProcessor() + { + $processor = function () {}; + $this->handler->expects($this->once()) + ->method('popProcessor') + ->willReturn($processor); + + $this->assertEquals($processor, $this->wrapper->popProcessor()); + } + + public function testSetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('setFormatter') + ->with($formatter); + + $this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter)); + } + + public function testGetFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $this->handler->expects($this->once()) + ->method('getFormatter') + ->willReturn($formatter); + + $this->assertEquals($formatter, $this->wrapper->getFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php new file mode 100644 index 0000000000..52dc9daca2 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandlerTest extends TestCase +{ + private $res; + /** @var HipChatHandler */ + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteCustomHostHeader() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2() + { + $this->createHandler('myToken', 'room1', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testWriteV2Notify() + { + $this->createHandler('myToken', 'room1', 'Monolog', true, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room1\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + public function testRoomSpaces() + { + $this->createHandler('myToken', 'room name', 'Monolog', false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v2\/room\/room%20name\/notification\?auth_token=.* HTTP\/1.1\\r\\nHost: hipchat.foo.bar\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + public function testWriteContentV1WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=$/', $content); + + return $content; + } + + /** + * @depends testWriteCustomHostHeader + */ + public function testWriteContentNotify($content) + { + $this->assertRegexp('/notify=1&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2 + */ + public function testWriteContentV2($content) + { + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + /** + * @depends testWriteV2Notify + */ + public function testWriteContentV2Notify($content) + { + $this->assertRegexp('/notify=true&message=test1&message_format=text&color=red&from=Monolog$/', $content); + } + + public function testWriteContentV2WithoutName() + { + $this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v2'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content); + + return $content; + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteTruncatesLongMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, str_repeat('abcde', 2000))); + fseek($this->res, 0); + $content = fread($this->res, 12000); + + $this->assertRegexp('/message='.str_repeat('abcde', 1900).'\+%5Btruncated%5D/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteWithErrorLevelsAndColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, 'gray'), + array(Logger::INFO, 'green'), + array(Logger::WARNING, 'yellow'), + array(Logger::ERROR, 'red'), + array(Logger::CRITICAL, 'red'), + array(Logger::ALERT, 'red'), + array(Logger::EMERGENCY,'red'), + array(Logger::NOTICE, 'green'), + ); + } + + /** + * @dataProvider provideBatchRecords + */ + public function testHandleBatch($records, $expectedColor) + { + $this->createHandler(); + + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideBatchRecords() + { + return array( + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime()), + ), + 'red', + ), + array( + array( + array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'yellow', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()), + ), + 'green', + ), + array( + array( + array('level' => Logger::DEBUG, 'message' => 'Just debugging.', 'level_name' => 'debug', 'datetime' => new \DateTime()), + ), + 'gray', + ), + ); + } + + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false, $host = 'api.hipchat.com', $version = 'v1') + { + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG, true, true, 'text', $host, $version); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\HipChatHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateWithTooLongName() + { + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere'); + } + + public function testCreateWithTooLongNameV2() + { + // creating a handler with too long of a name but using the v2 api doesn't matter. + $hipChatHandler = new HipChatHandler('token', 'room', 'SixteenCharsHere', false, Logger::CRITICAL, true, true, 'test', 'api.hipchat.com', 'v2'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php new file mode 100644 index 0000000000..b2deb40ae9 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/LogEntriesHandlerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var LogEntriesHandler + */ + private $handler; + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test')); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] test.CRITICAL: Critical write test/', $content); + } + + public function testWriteBatchContent() + { + $records = array( + $this->getRecord(), + $this->getRecord(), + $this->getRecord(), + ); + $this->createHandler(); + $this->handler->handleBatch($records); + + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] .* \[\] \[\]\n){3}/', $content); + } + + private function createHandler() + { + $useSSL = extension_loaded('openssl'); + $args = array('testToken', $useSSL, Logger::DEBUG, true); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\LogEntriesHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $args + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php new file mode 100644 index 0000000000..6754f3d623 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class MailHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatch() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once()) + ->method('formatBatch'); // Each record is formatted + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->once()) + ->method('send'); + $handler->expects($this->never()) + ->method('write'); // write is for individual records + + $handler->setFormatter($formatter); + + $handler->handleBatch($this->getMultipleRecords()); + } + + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->never()) + ->method('send'); + $handler->setLevel(Logger::ERROR); + + $handler->handleBatch($records); + } + + /** + * @covers Monolog\Handler\MailHandler::write + */ + public function testHandle() + { + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + + $record = $this->getRecord(); + $records = array($record); + $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n"; + + $handler->expects($this->once()) + ->method('send') + ->with($records[0]['formatted'], $records); + + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php new file mode 100644 index 0000000000..a0833225d3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Raven_Client; + +class MockRavenClient extends Raven_Client +{ + public function capture($data, $stack, $vars = null) + { + $data = array_merge($this->get_user_data(), $data); + $this->lastData = $data; + $this->lastStack = $stack; + } + + public $lastData; + public $lastStack; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php new file mode 100644 index 0000000000..0fdef63aaf --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class MongoDBHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDBHandler(new \stdClass(), 'DB', 'Collection'); + } + + public function testHandle() + { + $mongo = $this->getMock('Mongo', array('selectCollection'), array(), '', false); + $collection = $this->getMock('stdClass', array('save')); + + $mongo->expects($this->once()) + ->method('selectCollection') + ->with('DB', 'Collection') + ->will($this->returnValue($collection)); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $collection->expects($this->once()) + ->method('save') + ->with($expected); + + $handler = new MongoDBHandler($mongo, 'DB', 'Collection'); + $handler->handle($record); + } +} + +if (!class_exists('Mongo')) { + class Mongo + { + public function selectCollection() + { + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php new file mode 100644 index 0000000000..ddf545dbee --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use InvalidArgumentException; + +function mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null) +{ + $GLOBALS['mail'][] = func_get_args(); +} + +class NativeMailerHandlerTest extends TestCase +{ + protected function setUp() + { + $GLOBALS['mail'] = array(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterArrayHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org")); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterContentTypeInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setContentType("text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterEncodingInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->setEncoding("utf-8\r\nFrom: faked@attacker.org"); + } + + public function testSend() + { + $to = 'spammer@example.org'; + $subject = 'dear victim'; + $from = 'receiver@example.org'; + + $mailer = new NativeMailerHandler($to, $subject, $from); + $mailer->handleBatch(array()); + + // batch is empty, nothing sent + $this->assertEmpty($GLOBALS['mail']); + + // non-empty batch + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame($to, $params[0]); + $this->assertSame($subject, $params[1]); + $this->assertStringEndsWith(" test.ERROR: Foo Bar Baz [] []\n", $params[2]); + $this->assertSame("From: $from\r\nContent-type: text/plain; charset=utf-8\r\n", $params[3]); + $this->assertSame('', $params[4]); + } + + public function testMessageSubjectFormatting() + { + $mailer = new NativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org'); + $mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz")); + $this->assertNotEmpty($GLOBALS['mail']); + $this->assertInternalType('array', $GLOBALS['mail']); + $this->assertArrayHasKey('0', $GLOBALS['mail']); + $params = $GLOBALS['mail'][0]; + $this->assertCount(5, $params); + $this->assertSame('Alert: ERROR Foo Bar Baz', $params[1]); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php new file mode 100644 index 0000000000..4d3a615f8c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\TestCase; +use Monolog\Logger; + +class NewRelicHandlerTest extends TestCase +{ + public static $appname; + public static $customParameters; + public static $transactionName; + + public function setUp() + { + self::$appname = null; + self::$customParameters = array(); + self::$transactionName = null; + } + + /** + * @expectedException Monolog\Handler\MissingExtensionException + */ + public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded() + { + $handler = new StubNewRelicHandlerWithoutExtension(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanHandleTheRecord() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanAddContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'))); + $this->assertEquals(array('context_a' => 'b'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedContextParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($this->getRecord( + Logger::ERROR, + 'log message', + array('a' => array('key1' => 'value1', 'key2' => 'value2')) + )); + $this->assertEquals( + array('context_a_key1' => 'value1', 'context_a_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $this->assertEquals(array('extra_c' => 'd'), self::$customParameters); + } + + public function testThehandlerCanAddExplodedExtraParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message'); + $record['extra'] = array('c' => array('key1' => 'value1', 'key2' => 'value2')); + + $handler = new StubNewRelicHandler(Logger::ERROR, true, self::$appname, true); + $handler->handle($record); + + $this->assertEquals( + array('extra_c_key1' => 'value1', 'extra_c_key2' => 'value2'), + self::$customParameters + ); + } + + public function testThehandlerCanAddExtraContextAndParamsToTheNewRelicTrace() + { + $record = $this->getRecord(Logger::ERROR, 'log message', array('a' => 'b')); + $record['extra'] = array('c' => 'd'); + + $handler = new StubNewRelicHandler(); + $handler->handle($record); + + $expected = array( + 'context_a' => 'b', + 'extra_c' => 'd', + ); + + $this->assertEquals($expected, self::$customParameters); + } + + public function testThehandlerCanHandleTheRecordsFormattedUsingTheLineFormatter() + { + $handler = new StubNewRelicHandler(); + $handler->setFormatter(new LineFormatter()); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testTheAppNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$appname); + } + + public function testTheAppNameCanBeInjectedFromtheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myAppName', self::$appname); + } + + public function testTheAppNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName'))); + + $this->assertEquals('logAppName', self::$appname); + } + + public function testTheTransactionNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, self::$transactionName); + } + + public function testTheTransactionNameCanBeInjectedFromTheConstructor() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myTransaction', self::$transactionName); + } + + public function testTheTransactionNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(Logger::DEBUG, false, null, false, 'myTransaction'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('transaction_name' => 'logTransactName'))); + + $this->assertEquals('logTransactName', self::$transactionName); + } +} + +class StubNewRelicHandlerWithoutExtension extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return false; + } +} + +class StubNewRelicHandler extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return true; + } +} + +function newrelic_notice_error() +{ + return true; +} + +function newrelic_set_appname($appname) +{ + return NewRelicHandlerTest::$appname = $appname; +} + +function newrelic_name_transaction($transactionName) +{ + return NewRelicHandlerTest::$transactionName = $transactionName; +} + +function newrelic_add_custom_parameter($key, $value) +{ + NewRelicHandlerTest::$customParameters[$key] = $value; + + return true; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php new file mode 100644 index 0000000000..292df78c7f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\NullHandler::handle + */ +class NullHandlerTest extends TestCase +{ + public function testHandle() + { + $handler = new NullHandler(); + $this->assertTrue($handler->handle($this->getRecord())); + } + + public function testHandleLowerLevelRecord() + { + $handler = new NullHandler(Logger::WARNING); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php new file mode 100644 index 0000000000..152573ef0b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PHPConsoleHandlerTest.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\ErrorHandler; +use Monolog\Logger; +use Monolog\TestCase; +use PhpConsole\Connector; +use PhpConsole\Dispatcher\Debug as DebugDispatcher; +use PhpConsole\Dispatcher\Errors as ErrorDispatcher; +use PhpConsole\Handler; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * @covers Monolog\Handler\PHPConsoleHandler + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + */ +class PHPConsoleHandlerTest extends TestCase +{ + /** @var Connector|PHPUnit_Framework_MockObject_MockObject */ + protected $connector; + /** @var DebugDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $debugDispatcher; + /** @var ErrorDispatcher|PHPUnit_Framework_MockObject_MockObject */ + protected $errorDispatcher; + + protected function setUp() + { + if (!class_exists('PhpConsole\Connector')) { + $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + $this->connector = $this->initConnectorMock(); + + $this->debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $this->connector->setDebugDispatcher($this->debugDispatcher); + + $this->errorDispatcher = $this->initErrorDispatcherMock($this->connector); + $this->connector->setErrorsDispatcher($this->errorDispatcher); + } + + protected function initDebugDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Debug') + ->disableOriginalConstructor() + ->setMethods(array('dispatchDebug')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initErrorDispatcherMock(Connector $connector) + { + return $this->getMockBuilder('PhpConsole\Dispatcher\Errors') + ->disableOriginalConstructor() + ->setMethods(array('dispatchError', 'dispatchException')) + ->setConstructorArgs(array($connector, $connector->getDumper())) + ->getMock(); + } + + protected function initConnectorMock() + { + $connector = $this->getMockBuilder('PhpConsole\Connector') + ->disableOriginalConstructor() + ->setMethods(array( + 'sendMessage', + 'onShutDown', + 'isActiveClient', + 'setSourcesBasePath', + 'setServerEncoding', + 'setPassword', + 'enableSslOnlyMode', + 'setAllowedIpMasks', + 'setHeadersLimit', + 'startEvalRequestsListener', + )) + ->getMock(); + + $connector->expects($this->any()) + ->method('isActiveClient') + ->will($this->returnValue(true)); + + return $connector; + } + + protected function getHandlerDefaultOption($name) + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $options = $handler->getOptions(); + + return $options[$name]; + } + + protected function initLogger($handlerOptions = array(), $level = Logger::DEBUG) + { + return new Logger('test', array( + new PHPConsoleHandler($handlerOptions, $this->connector, $level), + )); + } + + public function testInitWithDefaultConnector() + { + $handler = new PHPConsoleHandler(); + $this->assertEquals(spl_object_hash(Connector::getInstance()), spl_object_hash($handler->getConnector())); + } + + public function testInitWithCustomConnector() + { + $handler = new PHPConsoleHandler(array(), $this->connector); + $this->assertEquals(spl_object_hash($this->connector), spl_object_hash($handler->getConnector())); + } + + public function testDebug() + { + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with($this->equalTo('test')); + $this->initLogger()->addDebug('test'); + } + + public function testDebugContextInMessage() + { + $message = 'test'; + $tag = 'tag'; + $context = array($tag, 'custom' => mt_rand()); + $expectedMessage = $message . ' ' . json_encode(array_slice($context, 1)); + $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->equalTo($expectedMessage), + $this->equalTo($tag) + ); + $this->initLogger()->addDebug($message, $context); + } + + public function testDebugTags($tagsContextKeys = null) + { + $expectedTags = mt_rand(); + $logger = $this->initLogger($tagsContextKeys ? array('debugTagsKeysInContext' => $tagsContextKeys) : array()); + if (!$tagsContextKeys) { + $tagsContextKeys = $this->getHandlerDefaultOption('debugTagsKeysInContext'); + } + foreach ($tagsContextKeys as $key) { + $debugDispatcher = $this->initDebugDispatcherMock($this->connector); + $debugDispatcher->expects($this->once())->method('dispatchDebug')->with( + $this->anything(), + $this->equalTo($expectedTags) + ); + $this->connector->setDebugDispatcher($debugDispatcher); + $logger->addDebug('test', array($key => $expectedTags)); + } + } + + public function testError($classesPartialsTraceIgnore = null) + { + $code = E_USER_NOTICE; + $message = 'message'; + $file = __FILE__; + $line = __LINE__; + $this->errorDispatcher->expects($this->once())->method('dispatchError')->with( + $this->equalTo($code), + $this->equalTo($message), + $this->equalTo($file), + $this->equalTo($line), + $classesPartialsTraceIgnore ?: $this->equalTo($this->getHandlerDefaultOption('classesPartialsTraceIgnore')) + ); + $errorHandler = ErrorHandler::register($this->initLogger($classesPartialsTraceIgnore ? array('classesPartialsTraceIgnore' => $classesPartialsTraceIgnore) : array()), false); + $errorHandler->registerErrorHandler(array(), false, E_USER_WARNING); + $errorHandler->handleError($code, $message, $file, $line); + } + + public function testException() + { + $e = new Exception(); + $this->errorDispatcher->expects($this->once())->method('dispatchException')->with( + $this->equalTo($e) + ); + $handler = $this->initLogger(); + $handler->log( + \Psr\Log\LogLevel::ERROR, + sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), + array('exception' => $e) + ); + } + + /** + * @expectedException Exception + */ + public function testWrongOptionsThrowsException() + { + new PHPConsoleHandler(array('xxx' => 1)); + } + + public function testOptionEnabled() + { + $this->debugDispatcher->expects($this->never())->method('dispatchDebug'); + $this->initLogger(array('enabled' => false))->addDebug('test'); + } + + public function testOptionClassesPartialsTraceIgnore() + { + $this->testError(array('Class', 'Namespace\\')); + } + + public function testOptionDebugTagsKeysInContext() + { + $this->testDebugTags(array('key1', 'key2')); + } + + public function testOptionUseOwnErrorsAndExceptionsHandler() + { + $this->initLogger(array('useOwnErrorsHandler' => true, 'useOwnExceptionsHandler' => true)); + $this->assertEquals(array(Handler::getInstance(), 'handleError'), set_error_handler(function () { + })); + $this->assertEquals(array(Handler::getInstance(), 'handleException'), set_exception_handler(function () { + })); + } + + public static function provideConnectorMethodsOptionsSets() + { + return array( + array('sourcesBasePath', 'setSourcesBasePath', __DIR__), + array('serverEncoding', 'setServerEncoding', 'cp1251'), + array('password', 'setPassword', '******'), + array('enableSslOnlyMode', 'enableSslOnlyMode', true, false), + array('ipMasks', 'setAllowedIpMasks', array('127.0.0.*')), + array('headersLimit', 'setHeadersLimit', 2500), + array('enableEvalListener', 'startEvalRequestsListener', true, false), + ); + } + + /** + * @dataProvider provideConnectorMethodsOptionsSets + */ + public function testOptionCallsConnectorMethod($option, $method, $value, $isArgument = true) + { + $expectCall = $this->connector->expects($this->once())->method($method); + if ($isArgument) { + $expectCall->with($value); + } + new PHPConsoleHandler(array($option => $value), $this->connector); + } + + public function testOptionDetectDumpTraceAndSource() + { + new PHPConsoleHandler(array('detectDumpTraceAndSource' => true), $this->connector); + $this->assertTrue($this->connector->getDebugDispatcher()->detectTraceAndSource); + } + + public static function provideDumperOptionsValues() + { + return array( + array('dumperLevelLimit', 'levelLimit', 1001), + array('dumperItemsCountLimit', 'itemsCountLimit', 1002), + array('dumperItemSizeLimit', 'itemSizeLimit', 1003), + array('dumperDumpSizeLimit', 'dumpSizeLimit', 1004), + array('dumperDetectCallbacks', 'detectCallbacks', true), + ); + } + + /** + * @dataProvider provideDumperOptionsValues + */ + public function testDumperOptions($option, $dumperProperty, $value) + { + new PHPConsoleHandler(array($option => $value), $this->connector); + $this->assertEquals($value, $this->connector->getDumper()->$dumperProperty); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php new file mode 100644 index 0000000000..64eaab16e8 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PsrHandlerTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\PsrHandler::handle + */ +class PsrHandlerTest extends TestCase +{ + public function logLevelProvider() + { + $levels = array(); + $monologLogger = new Logger(''); + + foreach ($monologLogger->getLevels() as $levelName => $level) { + $levels[] = array($levelName, $level); + } + + return $levels; + } + + /** + * @dataProvider logLevelProvider + */ + public function testHandlesAllLevels($levelName, $level) + { + $message = 'Hello, world! ' . $level; + $context = array('foo' => 'bar', 'level' => $level); + + $psrLogger = $this->getMock('Psr\Log\NullLogger'); + $psrLogger->expects($this->once()) + ->method('log') + ->with(strtolower($levelName), $message, $context); + + $handler = new PsrHandler($psrLogger); + $handler->handle(array('level' => $level, 'level_name' => $levelName, 'message' => $message, 'context' => $context)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php new file mode 100644 index 0000000000..56df474a76 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * Almost all examples (expected header, titles, messages) taken from + * https://www.pushover.net/api + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandlerTest extends TestCase +{ + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content); + } + + public function testWriteWithComplexTitle() + { + $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteWithTooLongMessage() + { + $message = str_pad('test', 520, 'a'); + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, $message)); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $expectedMessage = substr($message, 0, 505); + + $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content); + } + + public function testWriteWithHighPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content); + } + + public function testWriteWithEmergencyPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + public function testWriteToMultipleUsers() + { + $this->createHandler('myToken', array('userA', 'userB')); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content); + $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog') + { + $constructorArgs = array($token, $user, $title); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\PushoverHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php new file mode 100644 index 0000000000..26d212b964 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RavenHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('Raven_Client')) { + $this->markTestSkipped('raven/raven not installed'); + } + + require_once __DIR__ . '/MockRavenClient.php'; + } + + /** + * @covers Monolog\Handler\RavenHandler::__construct + */ + public function testConstruct() + { + $handler = new RavenHandler($this->getRavenClient()); + $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); + } + + protected function getHandler($ravenClient) + { + $handler = new RavenHandler($ravenClient); + + return $handler; + } + + protected function getRavenClient() + { + $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; + + return new MockRavenClient($dsn); + } + + public function testDebug() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::DEBUG, 'A test debug message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testWarning() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::WARNING, 'A test warning message'); + $handler->handle($record); + + $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testTag() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $tags = array(1, 2, 'foo'); + $record = $this->getRecord(Logger::INFO, 'test', array('tags' => $tags)); + $handler->handle($record); + + $this->assertEquals($tags, $ravenClient->lastData['tags']); + } + + public function testExtraParameters() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $checksum = '098f6bcd4621d373cade4e832627b4f6'; + $release = '05a671c66aefea124cc08b76ea6d30bb'; + $eventId = '31423'; + $record = $this->getRecord(Logger::INFO, 'test', array('checksum' => $checksum, 'release' => $release, 'event_id' => $eventId)); + $handler->handle($record); + + $this->assertEquals($checksum, $ravenClient->lastData['checksum']); + $this->assertEquals($release, $ravenClient->lastData['release']); + $this->assertEquals($eventId, $ravenClient->lastData['event_id']); + } + + public function testFingerprint() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $fingerprint = array('{{ default }}', 'other value'); + $record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint)); + $handler->handle($record); + + $this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']); + } + + public function testUserContext() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $recordWithNoContext = $this->getRecord(Logger::INFO, 'test with default user context'); + // set user context 'externally' + + $user = array( + 'id' => '123', + 'email' => 'test@test.com', + ); + + $recordWithContext = $this->getRecord(Logger::INFO, 'test', array('user' => $user)); + + $ravenClient->user_context(array('id' => 'test_user_id')); + // handle context + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertInternalType('array', $ravenClient->context->user); + $this->assertSame('test_user_id', $ravenClient->context->user['id']); + + // handle with null context + $ravenClient->user_context(null); + $handler->handle($recordWithContext); + $this->assertEquals($user, $ravenClient->lastData['user']); + + // check to see if its reset + $handler->handle($recordWithNoContext); + $this->assertNull($ravenClient->context->user); + } + + public function testException() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + try { + $this->methodThatThrowsAnException(); + } catch (\Exception $e) { + $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); + $handler->handle($record); + } + + $this->assertEquals($record['message'], $ravenClient->lastData['message']); + } + + public function testHandleBatch() + { + $records = $this->getMultipleRecords(); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + $records[] = $this->getRecord(Logger::WARNING, 'warning'); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) { + return $record['level'] == 400; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testHandleBatchDoNothingIfRecordsAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient())); + $handler->expects($this->never())->method('handle'); + $handler->setLevel(Logger::ERROR); + $handler->handleBatch($records); + } + + public function testHandleBatchPicksProperMessage() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information 1'), + $this->getRecord(Logger::ERROR, 'error 1'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error 2'), + $this->getRecord(Logger::INFO, 'information 2'), + ); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format')->with($this->callback(function ($record) use ($records) { + return $record['message'] == 'error 1'; + })); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testGetSetBatchFormatter() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $handler->setBatchFormatter($formatter = new LineFormatter()); + $this->assertSame($formatter, $handler->getBatchFormatter()); + } + + public function testRelease() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + $release = 'v42.42.42'; + $handler->setRelease($release); + $record = $this->getRecord(Logger::INFO, 'test'); + $handler->handle($record); + $this->assertEquals($release, $ravenClient->lastData['release']); + + $localRelease = 'v41.41.41'; + $record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease)); + $handler->handle($record); + $this->assertEquals($localRelease, $ravenClient->lastData['release']); + } + + private function methodThatThrowsAnException() + { + throw new \Exception('This is an exception'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php new file mode 100644 index 0000000000..689d52785c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RedisHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidRedis() + { + new RedisHandler(new \stdClass(), 'key'); + } + + public function testConstructorShouldWorkWithPredis() + { + $redis = $this->getMock('Predis\Client'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testConstructorShouldWorkWithRedis() + { + $redis = $this->getMock('Redis'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testPredisHandle() + { + $redis = $this->getMock('Predis\Client', array('rpush')); + + // Predis\Client uses rpush + $redis->expects($this->once()) + ->method('rpush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandle() + { + $redis = $this->getMock('Redis', array('rpush')); + + // Redis uses rPush + $redis->expects($this->once()) + ->method('rPush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandleCapped() + { + $redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'exec')); + + // Redis uses multi + $redis->expects($this->once()) + ->method('multi') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + $redis->expects($this->once()) + ->method('exec') + ->will($this->returnSelf()); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testPredisHandleCapped() + { + $redis = $this->getMock('Predis\Client', array('transaction')); + + $redisTransaction = $this->getMock('Predis\Client', array('rpush', 'ltrim')); + + $redisTransaction->expects($this->once()) + ->method('rpush') + ->will($this->returnSelf()); + + $redisTransaction->expects($this->once()) + ->method('ltrim') + ->will($this->returnSelf()); + + // Redis uses multi + $redis->expects($this->once()) + ->method('transaction') + ->will($this->returnCallback(function ($cb) use ($redisTransaction) { + $cb($redisTransaction); + })); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key', Logger::DEBUG, true, 10); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php new file mode 100644 index 0000000000..f302e917d3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RollbarHandlerTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; +use Monolog\TestCase; +use Monolog\Logger; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * @author Erik Johansson + * @see https://rollbar.com/docs/notifier/rollbar-php/ + * + * @coversDefaultClass Monolog\Handler\RollbarHandler + */ +class RollbarHandlerTest extends TestCase +{ + /** + * @var MockObject + */ + private $rollbarNotifier; + + /** + * @var array + */ + public $reportedExceptionArguments = null; + + protected function setUp() + { + parent::setUp(); + + $this->setupRollbarNotifierMock(); + } + + /** + * When reporting exceptions to Rollbar the + * level has to be set in the payload data + */ + public function testExceptionLogLevel() + { + $handler = $this->createHandler(); + + $handler->handle($this->createExceptionRecord(Logger::DEBUG)); + + $this->assertEquals('debug', $this->reportedExceptionArguments['payload']['level']); + } + + private function setupRollbarNotifierMock() + { + $this->rollbarNotifier = $this->getMockBuilder('RollbarNotifier') + ->setMethods(array('report_message', 'report_exception', 'flush')) + ->getMock(); + + $that = $this; + + $this->rollbarNotifier + ->expects($this->any()) + ->method('report_exception') + ->willReturnCallback(function ($exception, $context, $payload) use ($that) { + $that->reportedExceptionArguments = compact('exception', 'context', 'payload'); + }); + } + + private function createHandler() + { + return new RollbarHandler($this->rollbarNotifier, Logger::DEBUG); + } + + private function createExceptionRecord($level = Logger::DEBUG, $message = 'test', $exception = null) + { + return $this->getRecord($level, $message, array( + 'exception' => $exception ?: new Exception() + )); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php new file mode 100644 index 0000000000..f1feb22810 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use PHPUnit_Framework_Error_Deprecated; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class RotatingFileHandlerTest extends TestCase +{ + /** + * This var should be private but then the anonymous function + * in the `setUp` method won't be able to set it. `$this` cant't + * be used in the anonymous function in `setUp` because PHP 5.3 + * does not support it. + */ + public $lastError; + + public function setUp() + { + $dir = __DIR__.'/Fixtures'; + chmod($dir, 0777); + if (!is_writable($dir)) { + $this->markTestSkipped($dir.' must be writable to test the RotatingFileHandler.'); + } + $this->lastError = null; + $self = $this; + // workaround with &$self used for PHP 5.3 + set_error_handler(function($code, $message) use (&$self) { + $self->lastError = array( + 'code' => $code, + 'message' => $message, + ); + }); + } + + private function assertErrorWasTriggered($code, $message) + { + if (empty($this->lastError)) { + $this->fail( + sprintf( + 'Failed asserting that error with code `%d` and message `%s` was triggered', + $code, + $message + ) + ); + } + $this->assertEquals($code, $this->lastError['code'], sprintf('Expected an error with code %d to be triggered, got `%s` instead', $code, $this->lastError['code'])); + $this->assertEquals($message, $this->lastError['message'], sprintf('Expected an error with message `%d` to be triggered, got `%s` instead', $message, $this->lastError['message'])); + } + + public function testRotationCreatesNewFile() + { + touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + $this->assertTrue(file_exists($log)); + $this->assertEquals('test', file_get_contents($log)); + } + + /** + * @dataProvider rotationTests + */ + public function testRotation($createFile, $dateFormat, $timeCallback) + { + touch($old1 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-1)).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-2)).'.rot'); + touch($old3 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-3)).'.rot'); + touch($old4 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-4)).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot'; + + if ($createFile) { + touch($log); + } + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + $handler->handle($this->getRecord()); + + $handler->close(); + + $this->assertTrue(file_exists($log)); + $this->assertTrue(file_exists($old1)); + $this->assertEquals($createFile, file_exists($old2)); + $this->assertEquals($createFile, file_exists($old3)); + $this->assertEquals($createFile, file_exists($old4)); + $this->assertEquals('test', file_get_contents($log)); + } + + public function rotationTests() + { + $now = time(); + $dayCallback = function($ago) use ($now) { + return $now + 86400 * $ago; + }; + $monthCallback = function($ago) { + return gmmktime(0, 0, 0, date('n') + $ago, 1, date('Y')); + }; + $yearCallback = function($ago) { + return gmmktime(0, 0, 0, 1, 1, date('Y') + $ago); + }; + + return array( + 'Rotation is triggered when the file of the current day is not present' + => array(true, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + 'Rotation is not triggered when the file of the current day is already present' + => array(false, RotatingFileHandler::FILE_PER_DAY, $dayCallback), + + 'Rotation is triggered when the file of the current month is not present' + => array(true, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + 'Rotation is not triggered when the file of the current month is already present' + => array(false, RotatingFileHandler::FILE_PER_MONTH, $monthCallback), + + 'Rotation is triggered when the file of the current year is not present' + => array(true, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + 'Rotation is not triggered when the file of the current year is already present' + => array(false, RotatingFileHandler::FILE_PER_YEAR, $yearCallback), + ); + } + + /** + * @dataProvider dateFormatProvider + */ + public function testAllowOnlyFixedDefinedDateFormats($dateFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat('{filename}-{date}', $dateFormat); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid date format - format must be one of RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), '. + 'RotatingFileHandler::FILE_PER_MONTH ("Y-m") or RotatingFileHandler::FILE_PER_YEAR ("Y"), '. + 'or you can set one of the date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + } + + public function dateFormatProvider() + { + return array( + array(RotatingFileHandler::FILE_PER_DAY, true), + array(RotatingFileHandler::FILE_PER_MONTH, true), + array(RotatingFileHandler::FILE_PER_YEAR, true), + array('m-d-Y', false), + array('Y-m-d-h-i', false) + ); + } + + /** + * @dataProvider filenameFormatProvider + */ + public function testDisallowFilenameFormatsWithoutDate($filenameFormat, $valid) + { + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY); + if (!$valid) { + $this->assertErrorWasTriggered( + E_USER_DEPRECATED, + 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + } + + public function filenameFormatProvider() + { + return array( + array('{filename}', false), + array('{filename}-{date}', true), + array('{date}', true), + array('foobar-{date}', true), + array('foo-{date}-bar', true), + array('{date}-foobar', true), + array('foobar', false), + ); + } + + public function testReuseCurrentFile() + { + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + file_put_contents($log, "foo"); + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + $this->assertEquals('footest', file_get_contents($log)); + } + + public function tearDown() + { + foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) { + unlink($file); + } + restore_error_handler(); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php new file mode 100644 index 0000000000..b354cee179 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SamplingHandlerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @covers Monolog\Handler\SamplingHandler::handle + */ +class SamplingHandlerTest extends TestCase +{ + public function testHandle() + { + $testHandler = new TestHandler(); + $handler = new SamplingHandler($testHandler, 2); + for ($i = 0; $i < 10000; $i++) { + $handler->handle($this->getRecord()); + } + $count = count($testHandler->getRecords()); + // $count should be half of 10k, so between 4k and 6k + $this->assertLessThan(6000, $count); + $this->assertGreaterThan(4000, $count); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php new file mode 100644 index 0000000000..e1aa96d71e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -0,0 +1,387 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass Monolog\Handler\Slack\SlackRecord + */ +class SlackRecordTest extends TestCase +{ + private $jsonPrettyPrintFlag; + + protected function setUp() + { + $this->jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + } + + public function dataGetAttachmentColor() + { + return array( + array(Logger::DEBUG, SlackRecord::COLOR_DEFAULT), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY, SlackRecord::COLOR_DANGER), + ); + } + + /** + * @dataProvider dataGetAttachmentColor + * @param int $logLevel + * @param string $expectedColour RGB hex color or name of Slack color + * @covers ::getAttachmentColor + */ + public function testGetAttachmentColor($logLevel, $expectedColour) + { + $slackRecord = new SlackRecord(); + $this->assertSame( + $expectedColour, + $slackRecord->getAttachmentColor($logLevel) + ); + } + + public function testAddsChannel() + { + $channel = '#test'; + $record = new SlackRecord($channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('channel', $data); + $this->assertSame($channel, $data['channel']); + } + + public function testNoUsernameByDefault() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('username', $data); + } + + /** + * @return array + */ + public function dataStringify() + { + $jsonPrettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; + + $multipleDimensions = array(array(1, 2)); + $numericKeys = array('library' => 'monolog'); + $singleDimension = array(1, 'Hello', 'Jordi'); + + return array( + array(array(), '[]'), + array($multipleDimensions, json_encode($multipleDimensions, $jsonPrettyPrintFlag)), + array($numericKeys, json_encode($numericKeys, $jsonPrettyPrintFlag)), + array($singleDimension, json_encode($singleDimension)) + ); + } + + /** + * @dataProvider dataStringify + */ + public function testStringify($fields, $expectedResult) + { + $slackRecord = new SlackRecord( + '#test', + 'test', + true, + null, + true, + true + ); + + $this->assertSame($expectedResult, $slackRecord->stringify($fields)); + } + + public function testAddsCustomUsername() + { + $username = 'Monolog bot'; + $record = new SlackRecord(null, $username); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame($username, $data['username']); + } + + public function testNoIcon() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('icon_emoji', $data); + } + + public function testAddsIcon() + { + $record = $this->getRecord(); + $slackRecord = new SlackRecord(null, null, false, 'ghost'); + $data = $slackRecord->getSlackData($record); + + $slackRecord2 = new SlackRecord(null, null, false, 'http://github.com/Seldaek/monolog'); + $data2 = $slackRecord2->getSlackData($record); + + $this->assertArrayHasKey('icon_emoji', $data); + $this->assertSame(':ghost:', $data['icon_emoji']); + $this->assertArrayHasKey('icon_url', $data2); + $this->assertSame('http://github.com/Seldaek/monolog', $data2['icon_url']); + } + + public function testAttachmentsNotPresentIfNoAttachment() + { + $record = new SlackRecord(null, null, false); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('attachments', $data); + } + + public function testAddsOneAttachment() + { + $record = new SlackRecord(); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('attachments', $data); + $this->assertArrayHasKey(0, $data['attachments']); + $this->assertInternalType('array', $data['attachments'][0]); + } + + public function testTextEqualsMessageIfNoAttachment() + { + $message = 'Test message'; + $record = new SlackRecord(null, null, false); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message, $data['text']); + } + + public function testTextEqualsFormatterOutput() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); + + $formatter2 = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter2 + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test1'; })); + + $message = 'Test message'; + $record = new SlackRecord(null, null, false, null, false, false, array(), $formatter); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test', $data['text']); + + $record->setFormatter($formatter2); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test1', $data['text']); + } + + public function testAddsFallbackAndTextToAttachment() + { + $message = 'Test message'; + $record = new SlackRecord(null); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertSame($message, $data['attachments'][0]['text']); + $this->assertSame($message, $data['attachments'][0]['fallback']); + } + + public function testMapsLevelToColorAttachmentColor() + { + $record = new SlackRecord(null); + $errorLoggerRecord = $this->getRecord(Logger::ERROR); + $emergencyLoggerRecord = $this->getRecord(Logger::EMERGENCY); + $warningLoggerRecord = $this->getRecord(Logger::WARNING); + $infoLoggerRecord = $this->getRecord(Logger::INFO); + $debugLoggerRecord = $this->getRecord(Logger::DEBUG); + + $data = $record->getSlackData($errorLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($emergencyLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($warningLoggerRecord); + $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']); + + $data = $record->getSlackData($infoLoggerRecord); + $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']); + + $data = $record->getSlackData($debugLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']); + } + + public function testAddsShortAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord(null, null, true, null, true); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame(array(), $attachment['fields']); + } + + public function testAddsShortAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $context = array('test' => 1); + $extra = array('tags' => array('web')); + $record = new SlackRecord(null, null, true, null, true, true); + $loggerRecord = $this->getRecord($level, 'test', $context); + $loggerRecord['extra'] = $extra; + $data = $record->getSlackData($loggerRecord); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(2, $attachment['fields']); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame( + array( + array( + 'title' => 'Extra', + 'value' => sprintf('```%s```', json_encode($extra, $this->jsonPrettyPrintFlag)), + 'short' => false + ), + array( + 'title' => 'Context', + 'value' => sprintf('```%s```', json_encode($context, $this->jsonPrettyPrintFlag)), + 'short' => false + ) + ), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord(null, null, true, null); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(1, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + array(array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => false + )), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $context = array('test' => 1); + $extra = array('tags' => array('web')); + $record = new SlackRecord(null, null, true, null, false, true); + $loggerRecord = $this->getRecord($level, 'test', $context); + $loggerRecord['extra'] = $extra; + $data = $record->getSlackData($loggerRecord); + + $expectedFields = array( + array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => false, + ), + array( + 'title' => 'tags', + 'value' => sprintf('```%s```', json_encode($extra['tags'])), + 'short' => false + ), + array( + 'title' => 'test', + 'value' => $context['test'], + 'short' => false + ) + ); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(3, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + $expectedFields, + $attachment['fields'] + ); + } + + public function testAddsTimestampToAttachment() + { + $record = $this->getRecord(); + $slackRecord = new SlackRecord(); + $data = $slackRecord->getSlackData($this->getRecord()); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('ts', $attachment); + $this->assertSame($record['datetime']->getTimestamp(), $attachment['ts']); + } + + public function testExcludeExtraAndContextFields() + { + $record = $this->getRecord( + Logger::WARNING, + 'test', + array('info' => array('library' => 'monolog', 'author' => 'Jordi')) + ); + $record['extra'] = array('tags' => array('web', 'cli')); + + $slackRecord = new SlackRecord(null, null, true, null, false, true, array('context.info.library', 'extra.tags.1')); + $data = $slackRecord->getSlackData($record); + $attachment = $data['attachments'][0]; + + $expected = array( + array( + 'title' => 'info', + 'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)), + 'short' => false + ), + array( + 'title' => 'tags', + 'value' => sprintf('```%s```', json_encode(array('web'))), + 'short' => false + ), + ); + + foreach ($expected as $field) { + $this->assertNotFalse(array_search($field, $attachment['fields'])); + break; + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php new file mode 100644 index 0000000000..b12b01f45b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SlackHandlerTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandlerTest extends TestCase +{ + /** + * @var resource + */ + private $res; + + /** + * @var SlackHandler + */ + private $handler; + + public function setUp() + { + if (!extension_loaded('openssl')) { + $this->markTestSkipped('This test requires openssl to run'); + } + } + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/api\/chat.postMessage HTTP\/1.1\\r\\nHost: slack.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + } + + public function testWriteContent() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegExp('/username=Monolog/', $content); + $this->assertRegExp('/channel=channel1/', $content); + $this->assertRegExp('/token=myToken/', $content); + $this->assertRegExp('/attachments/', $content); + } + + public function testWriteContentUsesFormatterIfProvided() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->setFormatter(new LineFormatter('foo--%message%')); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test2')); + fseek($this->res, 0); + $content2 = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + $this->assertRegexp('/text=foo--test2/', $content2); + } + + public function testWriteContentWithEmoji() + { + $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien'); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/icon_emoji=%3Aalien%3A/', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteContentWithColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/%22color%22%3A%22'.$expectedColor.'/', $content); + } + + public function testWriteContentWithPlainTextMessage() + { + $this->createHandler('myToken', 'channel1', 'Monolog', false); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/text=test1/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, urlencode(SlackRecord::COLOR_DEFAULT)), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY,SlackRecord::COLOR_DANGER), + ); + } + + private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeExtra = false) + { + $constructorArgs = array($token, $channel, $username, $useAttachment, $iconEmoji, Logger::DEBUG, true, $useShortAttachment, $includeExtra); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\SlackHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php new file mode 100644 index 0000000000..c9229e26f3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SlackWebhookHandlerTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @coversDefaultClass Monolog\Handler\SlackWebhookHandler + */ +class SlackWebhookHandlerTest extends TestCase +{ + const WEBHOOK_URL = 'https://hooks.slack.com/services/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E'; + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorMinimal() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $record = $this->getRecord(); + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'attachments' => array( + array( + 'fallback' => 'test', + 'text' => 'test', + 'color' => SlackRecord::COLOR_WARNING, + 'fields' => array( + array( + 'title' => 'Level', + 'value' => 'WARNING', + 'short' => false, + ), + ), + 'title' => 'Message', + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + ), + ), + ), $slackRecord->getSlackData($record)); + } + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorFull() + { + $handler = new SlackWebhookHandler( + self::WEBHOOK_URL, + 'test-channel', + 'test-username', + false, + ':ghost:', + false, + false, + Logger::DEBUG, + false + ); + + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'test-username', + 'text' => 'test', + 'channel' => 'test-channel', + 'icon_emoji' => ':ghost:', + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::getFormatter + */ + public function testGetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = $handler->getFormatter(); + $this->assertInstanceOf('Monolog\Formatter\FormatterInterface', $formatter); + } + + /** + * @covers ::setFormatter + */ + public function testSetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = new LineFormatter(); + $handler->setFormatter($formatter); + $this->assertSame($formatter, $handler->getFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php new file mode 100644 index 0000000000..b1b02bde30 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SlackbotHandlerTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + * @coversDefaultClass Monolog\Handler\SlackbotHandler + */ +class SlackbotHandlerTest extends TestCase +{ + /** + * @covers ::__construct + */ + public function testConstructorMinimal() + { + $handler = new SlackbotHandler('test-team', 'test-token', 'test-channel'); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } + + /** + * @covers ::__construct + */ + public function testConstructorFull() + { + $handler = new SlackbotHandler( + 'test-team', + 'test-token', + 'test-channel', + Logger::DEBUG, + false + ); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php new file mode 100644 index 0000000000..1f9c1f2872 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Pablo de Leon Belloc + */ +class SocketHandlerTest extends TestCase +{ + /** + * @var Monolog\Handler\SocketHandler + */ + private $handler; + + /** + * @var resource + */ + private $res; + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidHostname() + { + $this->createHandler('garbage://here'); + $this->writeRecord('data'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(-1); + } + + public function testSetConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(10.1); + $this->assertEquals(10.1, $this->handler->getConnectionTimeout()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(-1); + } + + public function testSetTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getTimeout()); + } + + public function testSetWritingTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setWritingTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getWritingTimeout()); + } + + public function testSetConnectionString() + { + $this->createHandler('tcp://localhost:9090'); + $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnFsockopenError() + { + $this->setMockHandler(array('fsockopen')); + $this->handler->expects($this->once()) + ->method('fsockopen') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnPfsockopenError() + { + $this->setMockHandler(array('pfsockopen')); + $this->handler->expects($this->once()) + ->method('pfsockopen') + ->will($this->returnValue(false)); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetTimeout() + { + $this->setMockHandler(array('streamSetTimeout')); + $this->handler->expects($this->once()) + ->method('streamSetTimeout') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIfFwriteReturnsFalse() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => false, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsIfStreamTimesOut() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => true))); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIncompleteWrite() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $res = $this->res; + $callback = function ($string) use ($res) { + fclose($res); + + return strlen('Hello'); + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->writeRecord('Hello world'); + } + + public function testWriteWithMemoryFile() + { + $this->setMockHandler(); + $this->writeRecord('test1'); + $this->writeRecord('test2'); + $this->writeRecord('test3'); + fseek($this->res, 0); + $this->assertEquals('test1test2test3', fread($this->res, 1024)); + } + + public function testWriteWithMock() + { + $this->setMockHandler(array('fwrite')); + + $callback = function ($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + public function testClose() + { + $this->setMockHandler(); + $this->writeRecord('Hello world'); + $this->assertInternalType('resource', $this->res); + $this->handler->close(); + $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler"); + } + + public function testCloseDoesNotClosePersistentSocket() + { + $this->setMockHandler(); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + $this->assertTrue(is_resource($this->res)); + $this->handler->close(); + $this->assertTrue(is_resource($this->res)); + } + + /** + * @expectedException \RuntimeException + */ + public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $this->handler->expects($this->any()) + ->method('fwrite') + ->will($this->returnValue(0)); + + $this->handler->expects($this->any()) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->handler->setWritingTimeout(1); + + $this->writeRecord('Hello world'); + } + + private function createHandler($connectionString) + { + $this->handler = new SocketHandler($connectionString); + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + private function writeRecord($string) + { + $this->handler->handle($this->getRecord(Logger::WARNING, $string)); + } + + private function setMockHandler(array $methods = array()) + { + $this->res = fopen('php://memory', 'a'); + + $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout'); + $newMethods = array_diff($methods, $defaultMethods); + + $finalMethods = array_merge($defaultMethods, $newMethods); + + $this->handler = $this->getMock( + '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234') + ); + + if (!in_array('fsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('pfsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('pfsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('streamSetTimeout', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + } + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php new file mode 100644 index 0000000000..487030fe34 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class StreamHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWrite() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $handler->handle($this->getRecord(Logger::WARNING, 'test2')); + $handler->handle($this->getRecord(Logger::WARNING, 'test3')); + fseek($handle, 0); + $this->assertEquals('testtest2test3', fread($handle, 100)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testCloseKeepsExternalHandlersOpen() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertTrue(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testClose() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $streamProp = new \ReflectionProperty('Monolog\Handler\StreamHandler', 'stream'); + $streamProp->setAccessible(true); + $handle = $streamProp->getValue($handler); + + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertFalse(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteCreatesTheStreamResource() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteLocking() + { + $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'monolog_locked_log'; + $handler = new StreamHandler($temp, Logger::DEBUG, true, null, true); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException LogicException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteMissingResource() + { + $handler = new StreamHandler(null); + $handler->handle($this->getRecord()); + } + + public function invalidArgumentProvider() + { + return array( + array(1), + array(array()), + array(array('bogus://url')), + ); + } + + /** + * @dataProvider invalidArgumentProvider + * @expectedException InvalidArgumentException + * @covers Monolog\Handler\StreamHandler::__construct + */ + public function testWriteInvalidArgument($invalidArgument) + { + $handler = new StreamHandler($invalidArgument); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteInvalidResource() + { + $handler = new StreamHandler('bogus://url'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingResource() + { + $handler = new StreamHandler('ftp://foo/bar/baz/'.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingPath() + { + $handler = new StreamHandler(sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingFileResource() + { + $handler = new StreamHandler('file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatablePath() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('/foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException Exception + * @expectedExceptionMessageRegExp /There is no existing directory at/ + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingAndNotCreatableFileResource() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Permissions checks can not run on windows'); + } + $handler = new StreamHandler('file:///foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)); + $handler->handle($this->getRecord()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php new file mode 100644 index 0000000000..1d62940fa1 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SwiftMailerHandlerTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class SwiftMailerHandlerTest extends TestCase +{ + /** @var \Swift_Mailer|\PHPUnit_Framework_MockObject_MockObject */ + private $mailer; + + public function setUp() + { + $this->mailer = $this + ->getMockBuilder('Swift_Mailer') + ->disableOriginalConstructor() + ->getMock(); + } + + public function testMessageCreationIsLazyWhenUsingCallback() + { + $this->mailer->expects($this->never()) + ->method('send'); + + $callback = function () { + throw new \RuntimeException('Swift_Message creation callback should not have been called in this test'); + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + $records = array( + $this->getRecord(Logger::DEBUG), + $this->getRecord(Logger::INFO), + ); + $handler->handleBatch($records); + } + + public function testMessageCanBeCustomizedGivenLoggedData() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $expectedMessage = new \Swift_Message(); + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use ($expectedMessage) { + return $value instanceof \Swift_Message + && $value->getSubject() === 'Emergency' + && $value === $expectedMessage; + })); + + // Callback dynamically changes subject based on number of logged records + $callback = function ($content, array $records) use ($expectedMessage) { + $subject = count($records) > 0 ? 'Emergency' : 'Normal'; + $expectedMessage->setSubject($subject); + + return $expectedMessage; + }; + $handler = new SwiftMailerHandler($this->mailer, $callback); + + // Logging 1 record makes this an Emergency + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + } + + public function testMessageSubjectFormatting() + { + // Wire Mailer to expect a specific Swift_Message with a customized Subject + $messageTemplate = new \Swift_Message(); + $messageTemplate->setSubject('Alert: %level_name% %message%'); + $receivedMessage = null; + + $this->mailer->expects($this->once()) + ->method('send') + ->with($this->callback(function ($value) use (&$receivedMessage) { + $receivedMessage = $value; + return true; + })); + + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $records = array( + $this->getRecord(Logger::EMERGENCY), + ); + $handler->handleBatch($records); + + $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject()); + } + + public function testMessageHaveUniqueId() + { + $messageTemplate = new \Swift_Message(); + $handler = new SwiftMailerHandler($this->mailer, $messageTemplate); + + $method = new \ReflectionMethod('Monolog\Handler\SwiftMailerHandler', 'buildMessage'); + $method->setAccessible(true); + $method->invokeArgs($handler, array($messageTemplate, array())); + + $builtMessage1 = $method->invoke($handler, $messageTemplate, array()); + $builtMessage2 = $method->invoke($handler, $messageTemplate, array()); + + $this->assertFalse($builtMessage1->getId() === $builtMessage2->getId(), 'Two different messages have the same id'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php new file mode 100644 index 0000000000..8f9e46bf10 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +class SyslogHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstruct() + { + $handler = new SyslogHandler('test'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', 'user'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + } + + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstructInvalidFacility() + { + $this->setExpectedException('UnexpectedValueException'); + $handler = new SyslogHandler('test', 'unknown'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php new file mode 100644 index 0000000000..7ee8a98533 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogUdpHandlerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @requires extension sockets + */ +class SyslogUdpHandlerTest extends TestCase +{ + /** + * @expectedException UnexpectedValueException + */ + public function testWeValidateFacilities() + { + $handler = new SyslogUdpHandler("ip", null, "invalidFacility"); + } + + public function testWeSplitIntoLines() + { + $time = '2014-01-07T12:34'; + $pid = getmypid(); + $host = gethostname(); + + $handler = $this->getMockBuilder('\Monolog\Handler\SyslogUdpHandler') + ->setConstructorArgs(array("127.0.0.1", 514, "authpriv")) + ->setMethods(array('getDateTime')) + ->getMock(); + + $handler->method('getDateTime') + ->willReturn($time); + + $handler->setFormatter(new \Monolog\Formatter\ChromePHPFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->at(0)) + ->method('write') + ->with("lol", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 $time $host php $pid - - "); + $socket->expects($this->at(1)) + ->method('write') + ->with("hej", "<".(LOG_AUTHPRIV + LOG_WARNING).">1 $time $host php $pid - - "); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage("hej\nlol")); + } + + public function testSplitWorksOnEmptyMsg() + { + $handler = new SyslogUdpHandler("127.0.0.1", 514, "authpriv"); + $handler->setFormatter($this->getIdentityFormatter()); + + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('write'), array('lol', 'lol')); + $socket->expects($this->never()) + ->method('write'); + + $handler->setSocket($socket); + + $handler->handle($this->getRecordWithMessage(null)); + } + + protected function getRecordWithMessage($msg) + { + return array('message' => $msg, 'level' => \Monolog\Logger::WARNING, 'context' => null, 'extra' => array(), 'channel' => 'lol'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php new file mode 100644 index 0000000000..bfb8d3df28 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\TestHandler + */ +class TestHandlerTest extends TestCase +{ + /** + * @dataProvider methodProvider + */ + public function testHandler($method, $level) + { + $handler = new TestHandler; + $record = $this->getRecord($level, 'test'.$method); + $this->assertFalse($handler->hasRecords($level)); + $this->assertFalse($handler->hasRecord($record, $level)); + $this->assertFalse($handler->{'has'.$method}($record), 'has'.$method); + $this->assertFalse($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertFalse($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertFalse($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertFalse($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + $handler->handle($record); + + $this->assertFalse($handler->{'has'.$method}('bar'), 'has'.$method); + $this->assertTrue($handler->hasRecords($level)); + $this->assertTrue($handler->hasRecord($record, $level)); + $this->assertTrue($handler->{'has'.$method}($record), 'has'.$method); + $this->assertTrue($handler->{'has'.$method}('test'.$method), 'has'.$method); + $this->assertTrue($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains'); + $this->assertTrue($handler->{'has'.$method.'ThatPasses'}(function ($rec) { + return true; + }), 'has'.$method.'ThatPasses'); + $this->assertTrue($handler->{'has'.$method.'ThatMatches'}('/test\w+/')); + $this->assertTrue($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records'); + + $records = $handler->getRecords(); + unset($records[0]['formatted']); + $this->assertEquals(array($record), $records); + } + + public function methodProvider() + { + return array( + array('Emergency', Logger::EMERGENCY), + array('Alert' , Logger::ALERT), + array('Critical' , Logger::CRITICAL), + array('Error' , Logger::ERROR), + array('Warning' , Logger::WARNING), + array('Info' , Logger::INFO), + array('Notice' , Logger::NOTICE), + array('Debug' , Logger::DEBUG), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php new file mode 100644 index 0000000000..fa524d00f4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/UdpSocketTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Handler\SyslogUdp\UdpSocket; + +/** + * @requires extension sockets + */ +class UdpSocketTest extends TestCase +{ + public function testWeDoNotTruncateShortMessages() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $socket->expects($this->at(0)) + ->method('send') + ->with("HEADER: The quick brown fox jumps over the lazy dog"); + + $socket->write("The quick brown fox jumps over the lazy dog", "HEADER: "); + } + + public function testLongMessagesAreTruncated() + { + $socket = $this->getMock('\Monolog\Handler\SyslogUdp\UdpSocket', array('send'), array('lol', 'lol')); + + $truncatedString = str_repeat("derp", 16254).'d'; + + $socket->expects($this->exactly(1)) + ->method('send') + ->with("HEADER" . $truncatedString); + + $longString = str_repeat("derp", 20000); + + $socket->write($longString, "HEADER"); + } + + public function testDoubleCloseDoesNotError() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->close(); + } + + /** + * @expectedException LogicException + */ + public function testWriteAfterCloseErrors() + { + $socket = new UdpSocket('127.0.0.1', 514); + $socket->close(); + $socket->write('foo', "HEADER"); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php new file mode 100644 index 0000000000..8d37a1fcc1 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class WhatFailureGroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new WhatFailureGroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::__construct + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new WhatFailureGroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new WhatFailureGroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handle + */ + public function testHandleException() + { + $test = new TestHandler(); + $exception = new ExceptionTestHandler(); + $handler = new WhatFailureGroupHandler(array($exception, $test, $exception)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} + +class ExceptionTestHandler extends TestHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + parent::handle($record); + + throw new \Exception("ExceptionTestHandler::handle"); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php new file mode 100644 index 0000000000..69b001eae8 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class ZendMonitorHandlerTest extends TestCase +{ + protected $zendMonitorHandler; + + public function setUp() + { + if (!function_exists('zend_monitor_custom_event')) { + $this->markTestSkipped('ZendServer is not installed'); + } + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::write + */ + public function testWrite() + { + $record = $this->getRecord(); + $formatterResult = array( + 'message' => $record['message'], + ); + + $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler') + ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter')) + ->getMock(); + + $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter') + ->disableOriginalConstructor() + ->getMock(); + + $formatterMock->expects($this->once()) + ->method('format') + ->will($this->returnValue($formatterResult)); + + $zendMonitor->expects($this->once()) + ->method('getDefaultFormatter') + ->will($this->returnValue($formatterMock)); + + $levelMap = $zendMonitor->getLevelMap(); + + $zendMonitor->expects($this->once()) + ->method('writeZendMonitorCustomEvent') + ->with($levelMap[$record['level']], $record['message'], $formatterResult); + + $zendMonitor->handle($record); + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter + */ + public function testGetDefaultFormatterReturnsNormalizerFormatter() + { + $zendMonitor = new ZendMonitorHandler(); + $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php new file mode 100644 index 0000000000..1ecc34a0a6 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php @@ -0,0 +1,548 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Processor\WebProcessor; +use Monolog\Handler\TestHandler; + +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Logger::getName + */ + public function testGetName() + { + $logger = new Logger('foo'); + $this->assertEquals('foo', $logger->getName()); + } + + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::withName + */ + public function testWithName() + { + $first = new Logger('first', array($handler = new TestHandler())); + $second = $first->withName('second'); + + $this->assertSame('first', $first->getName()); + $this->assertSame('second', $second->getName()); + $this->assertSame($handler, $second->popHandler()); + } + + /** + * @covers Monolog\Logger::toMonologLevel + */ + public function testConvertPSR3ToMonologLevel() + { + $this->assertEquals(Logger::toMonologLevel('debug'), 100); + $this->assertEquals(Logger::toMonologLevel('info'), 200); + $this->assertEquals(Logger::toMonologLevel('notice'), 250); + $this->assertEquals(Logger::toMonologLevel('warning'), 300); + $this->assertEquals(Logger::toMonologLevel('error'), 400); + $this->assertEquals(Logger::toMonologLevel('critical'), 500); + $this->assertEquals(Logger::toMonologLevel('alert'), 550); + $this->assertEquals(Logger::toMonologLevel('emergency'), 600); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + + /** + * @covers Monolog\Logger::__construct + */ + public function testChannel() + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->addWarning('test'); + list($record) = $handler->getRecords(); + $this->assertEquals('foo', $record['channel']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLog() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle')); + $handler->expects($this->once()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->addWarning('test')); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLogNotHandled() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR)); + $handler->expects($this->never()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertFalse($logger->addWarning('test')); + } + + public function testHandlersInCtor() + { + $handler1 = new TestHandler; + $handler2 = new TestHandler; + $logger = new Logger(__METHOD__, array($handler1, $handler2)); + + $this->assertEquals($handler1, $logger->popHandler()); + $this->assertEquals($handler2, $logger->popHandler()); + } + + public function testProcessorsInCtor() + { + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + $logger = new Logger(__METHOD__, array(), array($processor1, $processor2)); + + $this->assertEquals($processor1, $logger->popProcessor()); + $this->assertEquals($processor2, $logger->popProcessor()); + } + + /** + * @covers Monolog\Logger::pushHandler + * @covers Monolog\Logger::popHandler + * @expectedException LogicException + */ + public function testPushPopHandler() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->pushHandler($handler2); + + $this->assertEquals($handler2, $logger->popHandler()); + $this->assertEquals($handler1, $logger->popHandler()); + $logger->popHandler(); + } + + /** + * @covers Monolog\Logger::setHandlers + */ + public function testSetHandlers() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->setHandlers(array($handler2)); + + // handler1 has been removed + $this->assertEquals(array($handler2), $logger->getHandlers()); + + $logger->setHandlers(array( + "AMapKey" => $handler1, + "Woop" => $handler2, + )); + + // Keys have been scrubbed + $this->assertEquals(array($handler1, $handler2), $logger->getHandlers()); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @covers Monolog\Logger::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = new Logger(__METHOD__); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $logger = new Logger(__METHOD__); + + $logger->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreExecuted() + { + $logger = new Logger(__METHOD__); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->pushProcessor(function ($record) { + $record['extra']['win'] = true; + + return $record; + }); + $logger->addError('test'); + list($record) = $handler->getRecords(); + $this->assertTrue($record['extra']['win']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreCalledOnlyOnce() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler); + + $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor') + ->disableOriginalConstructor() + ->setMethods(array('__invoke')) + ->getMock() + ; + $processor->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)) + ; + $logger->pushProcessor($processor); + + $logger->addError('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsNotCalledWhenNotHandled() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler); + $that = $this; + $logger->pushProcessor(function ($record) use ($that) { + $that->fail('The processor should not be called'); + }); + $logger->addAlert('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler3); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandlingWithAssocArray() + { + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + + $logger = new Logger(__METHOD__, array('last' => $handler3, 'second' => $handler2, 'first' => $handler1)); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testBubblingWhenTheHandlerReturnsFalse() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testNotBubblingWhenTheHandlerReturnsTrue() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::isHandling + */ + public function testIsHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + + $logger->pushHandler($handler1); + $this->assertFalse($logger->isHandling(Logger::DEBUG)); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + + $logger->pushHandler($handler2); + $this->assertTrue($logger->isHandling(Logger::DEBUG)); + } + + /** + * @dataProvider logMethodProvider + * @covers Monolog\Logger::addDebug + * @covers Monolog\Logger::addInfo + * @covers Monolog\Logger::addNotice + * @covers Monolog\Logger::addWarning + * @covers Monolog\Logger::addError + * @covers Monolog\Logger::addCritical + * @covers Monolog\Logger::addAlert + * @covers Monolog\Logger::addEmergency + * @covers Monolog\Logger::debug + * @covers Monolog\Logger::info + * @covers Monolog\Logger::notice + * @covers Monolog\Logger::warn + * @covers Monolog\Logger::err + * @covers Monolog\Logger::crit + * @covers Monolog\Logger::alert + * @covers Monolog\Logger::emerg + */ + public function testLogMethods($method, $expectedLevel) + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->{$method}('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($expectedLevel, $record['level']); + } + + public function logMethodProvider() + { + return array( + // monolog methods + array('addDebug', Logger::DEBUG), + array('addInfo', Logger::INFO), + array('addNotice', Logger::NOTICE), + array('addWarning', Logger::WARNING), + array('addError', Logger::ERROR), + array('addCritical', Logger::CRITICAL), + array('addAlert', Logger::ALERT), + array('addEmergency', Logger::EMERGENCY), + + // ZF/Sf2 compat methods + array('debug', Logger::DEBUG), + array('info', Logger::INFO), + array('notice', Logger::NOTICE), + array('warn', Logger::WARNING), + array('err', Logger::ERROR), + array('crit', Logger::CRITICAL), + array('alert', Logger::ALERT), + array('emerg', Logger::EMERGENCY), + ); + } + + /** + * @dataProvider setTimezoneProvider + * @covers Monolog\Logger::setTimezone + */ + public function testSetTimezone($tz) + { + Logger::setTimezone($tz); + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($tz, $record['datetime']->getTimezone()); + } + + public function setTimezoneProvider() + { + return array_map( + function ($tz) { return array(new \DateTimeZone($tz)); }, + \DateTimeZone::listIdentifiers() + ); + } + + /** + * @dataProvider useMicrosecondTimestampsProvider + * @covers Monolog\Logger::useMicrosecondTimestamps + * @covers Monolog\Logger::addRecord + */ + public function testUseMicrosecondTimestamps($micro, $assert) + { + $logger = new Logger('foo'); + $logger->useMicrosecondTimestamps($micro); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->info('test'); + list($record) = $handler->getRecords(); + $this->{$assert}('000000', $record['datetime']->format('u')); + } + + public function useMicrosecondTimestampsProvider() + { + return array( + // this has a very small chance of a false negative (1/10^6) + 'with microseconds' => array(true, 'assertNotSame'), + 'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php new file mode 100644 index 0000000000..5adb505dc4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/GitProcessorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class GitProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\GitProcessor::__invoke + */ + public function testProcessor() + { + $processor = new GitProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('git', $record['extra']); + $this->assertTrue(!is_array($record['extra']['git']['branch'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php new file mode 100644 index 0000000000..0dd411d75b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Acme; + +class Tester +{ + public function test($handler, $record) + { + $handler->handle($record); + } +} + +function tester($handler, $record) +{ + $handler->handle($record); +} + +namespace Monolog\Processor; + +use Monolog\Logger; +use Monolog\TestCase; +use Monolog\Handler\TestHandler; + +class IntrospectionProcessorTest extends TestCase +{ + public function getHandler() + { + $processor = new IntrospectionProcessor(); + $handler = new TestHandler(); + $handler->pushProcessor($processor); + + return $handler; + } + + public function testProcessorFromClass() + { + $handler = $this->getHandler(); + $tester = new \Acme\Tester; + $tester->test($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(18, $record['extra']['line']); + $this->assertEquals('Acme\Tester', $record['extra']['class']); + $this->assertEquals('test', $record['extra']['function']); + } + + public function testProcessorFromFunc() + { + $handler = $this->getHandler(); + \Acme\tester($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(24, $record['extra']['line']); + $this->assertEquals(null, $record['extra']['class']); + $this->assertEquals('Acme\tester', $record['extra']['function']); + } + + public function testLevelTooLow() + { + $input = array( + 'level' => Logger::DEBUG, + 'extra' => array(), + ); + + $expected = $input; + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelEqual() + { + $input = array( + 'level' => Logger::CRITICAL, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } + + public function testLevelHigher() + { + $input = array( + 'level' => Logger::EMERGENCY, + 'extra' => array(), + ); + + $expected = $input; + $expected['extra'] = array( + 'file' => null, + 'line' => null, + 'class' => 'ReflectionMethod', + 'function' => 'invokeArgs', + ); + + $processor = new IntrospectionProcessor(Logger::CRITICAL); + $actual = $processor($input); + + $this->assertEquals($expected, $actual); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php new file mode 100644 index 0000000000..eb666144ea --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryPeakUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryPeakUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']); + } + + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryPeakUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_peak_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_peak_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php new file mode 100644 index 0000000000..4692dbfc7e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']); + } + + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessorWithoutFormatting() + { + $processor = new MemoryUsageProcessor(true, false); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertInternalType('int', $record['extra']['memory_usage']); + $this->assertGreaterThan(0, $record['extra']['memory_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php new file mode 100644 index 0000000000..11f2b35a8a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MercurialProcessorTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MercurialProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MercurialProcessor::__invoke + */ + public function testProcessor() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + exec("where hg 2>NUL", $output, $result); + } else { + exec("which hg 2>/dev/null >/dev/null", $output, $result); + } + if ($result != 0) { + $this->markTestSkipped('hg is missing'); + return; + } + + `hg init`; + $processor = new MercurialProcessor(); + $record = $processor($this->getRecord()); + + $this->assertArrayHasKey('hg', $record['extra']); + $this->assertTrue(!is_array($record['extra']['hg']['branch'])); + $this->assertTrue(!is_array($record['extra']['hg']['revision'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php new file mode 100644 index 0000000000..458d2a33a6 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class ProcessIdProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\ProcessIdProcessor::__invoke + */ + public function testProcessor() + { + $processor = new ProcessIdProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('process_id', $record['extra']); + $this->assertInternalType('int', $record['extra']['process_id']); + $this->assertGreaterThan(0, $record['extra']['process_id']); + $this->assertEquals(getmypid(), $record['extra']['process_id']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php new file mode 100644 index 0000000000..029a0c02d6 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/PsrLogMessageProcessorTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getPairs + */ + public function testReplacement($val, $expected) + { + $proc = new PsrLogMessageProcessor; + + $message = $proc(array( + 'message' => '{foo}', + 'context' => array('foo' => $val), + )); + $this->assertEquals($expected, $message['message']); + } + + public function getPairs() + { + return array( + array('foo', 'foo'), + array('3', '3'), + array(3, '3'), + array(null, ''), + array(true, '1'), + array(false, ''), + array(new \stdClass, '[object stdClass]'), + array(array(), '[array]'), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php new file mode 100644 index 0000000000..0d860c61aa --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/TagProcessorTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class TagProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessor() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + $record = $processor($this->getRecord()); + + $this->assertEquals($tags, $record['extra']['tags']); + } + + /** + * @covers Monolog\Processor\TagProcessor::__invoke + */ + public function testProcessorTagModification() + { + $tags = array(1, 2, 3); + $processor = new TagProcessor($tags); + + $record = $processor($this->getRecord()); + $this->assertEquals($tags, $record['extra']['tags']); + + $processor->setTags(array('a', 'b')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b'), $record['extra']['tags']); + + $processor->addTags(array('a', 'c', 'foo' => 'bar')); + $record = $processor($this->getRecord()); + $this->assertEquals(array('a', 'b', 'a', 'c', 'foo' => 'bar'), $record['extra']['tags']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php new file mode 100644 index 0000000000..5d13058fd3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class UidProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\UidProcessor::__invoke + */ + public function testProcessor() + { + $processor = new UidProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('uid', $record['extra']); + } + + public function testGetUid() + { + $processor = new UidProcessor(10); + $this->assertEquals(10, strlen($processor->getUid())); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php new file mode 100644 index 0000000000..4105baf794 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class WebProcessorTest extends TestCase +{ + public function testProcessor() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'HTTP_REFERER' => 'D', + 'SERVER_NAME' => 'F', + 'UNIQUE_ID' => 'G', + ); + + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + $this->assertEquals($server['UNIQUE_ID'], $record['extra']['unique_id']); + } + + public function testProcessorDoNothingIfNoRequestUri() + { + $server = array( + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEmpty($record['extra']); + } + + public function testProcessorReturnNullIfNoHttpReferer() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertNull($record['extra']['referrer']); + } + + public function testProcessorDoesNotAddUniqueIdIfNotPresent() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertFalse(isset($record['extra']['unique_id'])); + } + + public function testProcessorAddsOnlyRequestedExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url', 'http_method')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'A', 'http_method' => 'C'), $record['extra']); + } + + public function testProcessorConfiguringOfExtraFields() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server, array('url' => 'REMOTE_ADDR')); + $record = $processor($this->getRecord()); + + $this->assertSame(array('url' => 'B'), $record['extra']); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidData() + { + new WebProcessor(new \stdClass); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php new file mode 100644 index 0000000000..ab89944962 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Log\Test\LoggerInterfaceTest; + +class PsrLogCompatTest extends LoggerInterfaceTest +{ + private $handler; + + public function getLogger() + { + $logger = new Logger('foo'); + $logger->pushHandler($handler = new TestHandler); + $logger->pushProcessor(new PsrLogMessageProcessor); + $handler->setFormatter(new LineFormatter('%level_name% %message%')); + + $this->handler = $handler; + + return $logger; + } + + public function getLogs() + { + $convert = function ($record) { + $lower = function ($match) { + return strtolower($match[0]); + }; + + return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']); + }; + + return array_map($convert, $this->handler->getRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/RegistryTest.php b/vendor/monolog/monolog/tests/Monolog/RegistryTest.php new file mode 100644 index 0000000000..15fdfbd2e7 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/RegistryTest.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class RegistryTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Registry::clear(); + } + + /** + * @dataProvider hasLoggerProvider + * @covers Monolog\Registry::hasLogger + */ + public function testHasLogger(array $loggersToAdd, array $loggersToCheck, array $expectedResult) + { + foreach ($loggersToAdd as $loggerToAdd) { + Registry::addLogger($loggerToAdd); + } + foreach ($loggersToCheck as $index => $loggerToCheck) { + $this->assertSame($expectedResult[$index], Registry::hasLogger($loggerToCheck)); + } + } + + public function hasLoggerProvider() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + $logger3 = new Logger('test3'); + + return array( + // only instances + array( + array($logger1), + array($logger1, $logger2), + array(true, false), + ), + // only names + array( + array($logger1), + array('test1', 'test2'), + array(true, false), + ), + // mixed case + array( + array($logger1, $logger2), + array('test1', $logger2, 'test3', $logger3), + array(true, true, false, false), + ), + ); + } + + /** + * @covers Monolog\Registry::clear + */ + public function testClearClears() + { + Registry::addLogger(new Logger('test1'), 'log'); + Registry::clear(); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance('log'); + } + + /** + * @dataProvider removedLoggerProvider + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::removeLogger + */ + public function testRemovesLogger($loggerToAdd, $remove) + { + Registry::addLogger($loggerToAdd); + Registry::removeLogger($remove); + + $this->setExpectedException('\InvalidArgumentException'); + Registry::getInstance($loggerToAdd->getName()); + } + + public function removedLoggerProvider() + { + $logger1 = new Logger('test1'); + + return array( + array($logger1, $logger1), + array($logger1, 'test1'), + ); + } + + /** + * @covers Monolog\Registry::addLogger + * @covers Monolog\Registry::getInstance + * @covers Monolog\Registry::__callStatic + */ + public function testGetsSameLogger() + { + $logger1 = new Logger('test1'); + $logger2 = new Logger('test2'); + + Registry::addLogger($logger1, 'test1'); + Registry::addLogger($logger2); + + $this->assertSame($logger1, Registry::getInstance('test1')); + $this->assertSame($logger2, Registry::test2()); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::getInstance + */ + public function testFailsOnNonExistantLogger() + { + Registry::getInstance('test1'); + } + + /** + * @covers Monolog\Registry::addLogger + */ + public function testReplacesLogger() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log', true); + + $this->assertSame($log2, Registry::getInstance('log')); + } + + /** + * @expectedException \InvalidArgumentException + * @covers Monolog\Registry::addLogger + */ + public function testFailsOnUnspecifiedReplacement() + { + $log1 = new Logger('test1'); + $log2 = new Logger('test2'); + + Registry::addLogger($log1, 'log'); + + Registry::addLogger($log2, 'log'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/TestCase.php b/vendor/monolog/monolog/tests/Monolog/TestCase.php new file mode 100644 index 0000000000..4eb7b4c9ee --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/TestCase.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array()) + { + return array( + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => array(), + ); + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ); + } + + /** + * @return Monolog\Formatter\FormatterInterface + */ + protected function getIdentityFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message']; })); + + return $formatter; + } +} diff --git a/vendor/myclabs/deep-copy/.gitattributes b/vendor/myclabs/deep-copy/.gitattributes new file mode 100755 index 0000000000..8018068b45 --- /dev/null +++ b/vendor/myclabs/deep-copy/.gitattributes @@ -0,0 +1,7 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.png binary + +tests/ export-ignore +phpunit.xml.dist export-ignore diff --git a/vendor/myclabs/deep-copy/.scrutinizer.yml b/vendor/myclabs/deep-copy/.scrutinizer.yml new file mode 100644 index 0000000000..6934299b6e --- /dev/null +++ b/vendor/myclabs/deep-copy/.scrutinizer.yml @@ -0,0 +1,4 @@ +build: + environment: + variables: + COMPOSER_ROOT_VERSION: '1.8.0' diff --git a/vendor/myclabs/deep-copy/.travis.yml b/vendor/myclabs/deep-copy/.travis.yml new file mode 100755 index 0000000000..88f9d2e8fe --- /dev/null +++ b/vendor/myclabs/deep-copy/.travis.yml @@ -0,0 +1,40 @@ +language: php + +sudo: false + +env: + global: + - COMPOSER_ROOT_VERSION=1.8.0 + +php: + - '7.1' + - '7.2' + - nightly + +matrix: + fast_finish: true + include: + - php: '7.1' + env: COMPOSER_FLAGS="--prefer-lowest" + allow_failures: + - php: nightly + +cache: + directories: + - $HOME/.composer/cache/files + +install: + - composer update --no-interaction --no-progress --no-suggest --prefer-dist $COMPOSER_FLAGS + - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.0/coveralls.phar + +before_script: + - mkdir -p build/logs + +script: + - vendor/bin/phpunit --coverage-clover build/logs/clover.xml + +after_script: + - php coveralls.phar -v + +notifications: + email: false diff --git a/vendor/myclabs/deep-copy/LICENSE b/vendor/myclabs/deep-copy/LICENSE new file mode 100644 index 0000000000..c3e835001c --- /dev/null +++ b/vendor/myclabs/deep-copy/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 My C-Sense + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/deep-copy/README.md b/vendor/myclabs/deep-copy/README.md new file mode 100644 index 0000000000..7abe5dc84a --- /dev/null +++ b/vendor/myclabs/deep-copy/README.md @@ -0,0 +1,376 @@ +# DeepCopy + +DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph. + +[![Build Status](https://travis-ci.org/myclabs/DeepCopy.png?branch=1.x)](https://travis-ci.org/myclabs/DeepCopy) +[![Coverage Status](https://coveralls.io/repos/myclabs/DeepCopy/badge.png?branch=1.x)](https://coveralls.io/r/myclabs/DeepCopy?branch=1.x) +[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/myclabs/DeepCopy/badges/quality-score.png?s=2747100c19b275f93a777e3297c6c12d1b68b934)](https://scrutinizer-ci.com/g/myclabs/DeepCopy/) +[![Total Downloads](https://poser.pugx.org/myclabs/deep-copy/downloads.svg)](https://packagist.org/packages/myclabs/deep-copy) + + +**You are browsing the 1.x version, this version is in maintenance mode only. Please check the new +[2.x](https://github.com/myclabs/DeepCopy/tree/2.x) version.** + + +## Table of Contents + +1. [How](#how) +1. [Why](#why) + 1. [Using simply `clone`](#using-simply-clone) + 1. [Overridding `__clone()`](#overridding-__clone) + 1. [With `DeepCopy`](#with-deepcopy) +1. [How it works](#how-it-works) +1. [Going further](#going-further) + 1. [Matchers](#matchers) + 1. [Property name](#property-name) + 1. [Specific property](#specific-property) + 1. [Type](#type) + 1. [Filters](#filters) + 1. [`SetNullFilter`](#setnullfilter-filter) + 1. [`KeepFilter`](#keepfilter-filter) + 1. [`DoctrineCollectionFilter`](#doctrinecollectionfilter-filter) + 1. [`DoctrineEmptyCollectionFilter`](#doctrineemptycollectionfilter-filter) + 1. [`DoctrineProxyFilter`](#doctrineproxyfilter-filter) + 1. [`ReplaceFilter`](#replacefilter-type-filter) + 1. [`ShallowCopyFilter`](#shallowcopyfilter-type-filter) +1. [Edge cases](#edge-cases) +1. [Contributing](#contributing) + 1. [Tests](#tests) + + +## How? + +Install with Composer: + +```json +composer require myclabs/deep-copy +``` + +Use simply: + +```php +use DeepCopy\DeepCopy; + +$copier = new DeepCopy(); +$myCopy = $copier->copy($myObject); +``` + + +## Why? + +- How do you create copies of your objects? + +```php +$myCopy = clone $myObject; +``` + +- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)? + +You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior +yourself. + +- But how do you handle **cycles** in the association graph? + +Now you're in for a big mess :( + +![association graph](doc/graph.png) + + +### Using simply `clone` + +![Using clone](doc/clone.png) + + +### Overridding `__clone()` + +![Overridding __clone](doc/deep-clone.png) + + +### With `DeepCopy` + +![With DeepCopy](doc/deep-copy.png) + + +## How it works + +DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it +keeps a hash map of all instances and thus preserves the object graph. + +To use it: + +```php +use function DeepCopy\deep_copy; + +$copy = deep_copy($var); +``` + +Alternatively, you can create your own `DeepCopy` instance to configure it differently for example: + +```php +use DeepCopy\DeepCopy; + +$copier = new DeepCopy(true); + +$copy = $copier->copy($var); +``` + +You may want to roll your own deep copy function: + +```php +namespace Acme; + +use DeepCopy\DeepCopy; + +function deep_copy($var) +{ + static $copier = null; + + if (null === $copier) { + $copier = new DeepCopy(true); + } + + return $copier->copy($var); +} +``` + + +## Going further + +You can add filters to customize the copy process. + +The method to add a filter is `DeepCopy\DeepCopy::addFilter($filter, $matcher)`, +with `$filter` implementing `DeepCopy\Filter\Filter` +and `$matcher` implementing `DeepCopy\Matcher\Matcher`. + +We provide some generic filters and matchers. + + +### Matchers + + - `DeepCopy\Matcher` applies on a object attribute. + - `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements. + + +#### Property name + +The `PropertyNameMatcher` will match a property by its name: + +```php +use DeepCopy\Matcher\PropertyNameMatcher; + +// Will apply a filter to any property of any objects named "id" +$matcher = new PropertyNameMatcher('id'); +``` + + +#### Specific property + +The `PropertyMatcher` will match a specific property of a specific class: + +```php +use DeepCopy\Matcher\PropertyMatcher; + +// Will apply a filter to the property "id" of any objects of the class "MyClass" +$matcher = new PropertyMatcher('MyClass', 'id'); +``` + + +#### Type + +The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of +[gettype()](http://php.net/manual/en/function.gettype.php) function): + +```php +use DeepCopy\TypeMatcher\TypeMatcher; + +// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection +$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection'); +``` + + +### Filters + +- `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher` +- `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher` + + +#### `SetNullFilter` (filter) + +Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have +any ID: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\SetNullFilter; +use DeepCopy\Matcher\PropertyNameMatcher; + +$object = MyClass::load(123); +echo $object->id; // 123 + +$copier = new DeepCopy(); +$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); + +$copy = $copier->copy($object); + +echo $copy->id; // null +``` + + +#### `KeepFilter` (filter) + +If you want a property to remain untouched (for example, an association to an object): + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\KeepFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category')); + +$copy = $copier->copy($object); +// $copy->category has not been touched +``` + + +#### `DoctrineCollectionFilter` (filter) + +If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter; +use DeepCopy\Matcher\PropertyTypeMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); + +$copy = $copier->copy($object); +``` + + +#### `DoctrineEmptyCollectionFilter` (filter) + +If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the +`DoctrineEmptyCollectionFilter` + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty')); + +$copy = $copier->copy($object); + +// $copy->myProperty will return an empty collection +``` + + +#### `DoctrineProxyFilter` (filter) + +If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a +Doctrine proxy class (...\\\_\_CG\_\_\Proxy). +You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class. +**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded +before other filters are applied!** + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; +use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineProxyFilter(), new DoctrineProxyMatcher()); + +$copy = $copier->copy($object); + +// $copy should now contain a clone of all entities, including those that were not yet fully loaded. +``` + + +#### `ReplaceFilter` (type filter) + +1. If you want to replace the value of a property: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\ReplaceFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$callback = function ($currentValue) { + return $currentValue . ' (copy)' +}; +$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title')); + +$copy = $copier->copy($object); + +// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)' +``` + +2. If you want to replace whole element: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\TypeFilter\ReplaceFilter; +use DeepCopy\TypeMatcher\TypeMatcher; + +$copier = new DeepCopy(); +$callback = function (MyClass $myClass) { + return get_class($myClass); +}; +$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass')); + +$copy = $copier->copy([new MyClass, 'some string', new MyClass]); + +// $copy will contain ['MyClass', 'some string', 'MyClass'] +``` + + +The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable. + + +#### `ShallowCopyFilter` (type filter) + +Stop *DeepCopy* from recursively copying element, using standard `clone` instead: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\TypeFilter\ShallowCopyFilter; +use DeepCopy\TypeMatcher\TypeMatcher; +use Mockery as m; + +$this->deepCopy = new DeepCopy(); +$this->deepCopy->addTypeFilter( + new ShallowCopyFilter, + new TypeMatcher(m\MockInterface::class) +); + +$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class)); +// All mocks will be just cloned, not deep copied +``` + + +## Edge cases + +The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are +not applied. There is two ways for you to handle them: + +- Implement your own `__clone()` method +- Use a filter with a type matcher + + +## Contributing + +DeepCopy is distributed under the MIT license. + + +### Tests + +Running the tests is simple: + +```php +vendor/bin/phpunit +``` diff --git a/vendor/myclabs/deep-copy/composer.json b/vendor/myclabs/deep-copy/composer.json new file mode 100644 index 0000000000..4108a23ba0 --- /dev/null +++ b/vendor/myclabs/deep-copy/composer.json @@ -0,0 +1,38 @@ +{ + "name": "myclabs/deep-copy", + "type": "library", + "description": "Create deep copies (clones) of your objects", + "keywords": ["clone", "copy", "duplicate", "object", "object graph"], + "license": "MIT", + + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "autoload-dev": { + "psr-4": { + "DeepCopy\\": "fixtures/", + "DeepCopyTest\\": "tests/DeepCopyTest/" + } + }, + + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + + "config": { + "sort-packages": true + } +} diff --git a/vendor/myclabs/deep-copy/doc/clone.png b/vendor/myclabs/deep-copy/doc/clone.png new file mode 100644 index 0000000000..376afd4912 Binary files /dev/null and b/vendor/myclabs/deep-copy/doc/clone.png differ diff --git a/vendor/myclabs/deep-copy/doc/deep-clone.png b/vendor/myclabs/deep-copy/doc/deep-clone.png new file mode 100644 index 0000000000..2b37a6d7d6 Binary files /dev/null and b/vendor/myclabs/deep-copy/doc/deep-clone.png differ diff --git a/vendor/myclabs/deep-copy/doc/deep-copy.png b/vendor/myclabs/deep-copy/doc/deep-copy.png new file mode 100644 index 0000000000..68c508ae45 Binary files /dev/null and b/vendor/myclabs/deep-copy/doc/deep-copy.png differ diff --git a/vendor/myclabs/deep-copy/doc/graph.png b/vendor/myclabs/deep-copy/doc/graph.png new file mode 100644 index 0000000000..4d5c9428f7 Binary files /dev/null and b/vendor/myclabs/deep-copy/doc/graph.png differ diff --git a/vendor/myclabs/deep-copy/fixtures/f001/A.php b/vendor/myclabs/deep-copy/fixtures/f001/A.php new file mode 100644 index 0000000000..648d5dfff4 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f001/A.php @@ -0,0 +1,20 @@ +aProp; + } + + public function setAProp($prop) + { + $this->aProp = $prop; + + return $this; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f001/B.php b/vendor/myclabs/deep-copy/fixtures/f001/B.php new file mode 100644 index 0000000000..462bb44e80 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f001/B.php @@ -0,0 +1,20 @@ +bProp; + } + + public function setBProp($prop) + { + $this->bProp = $prop; + + return $this; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f002/A.php b/vendor/myclabs/deep-copy/fixtures/f002/A.php new file mode 100644 index 0000000000..d9aa5c35b5 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f002/A.php @@ -0,0 +1,33 @@ +prop1; + } + + public function setProp1($prop) + { + $this->prop1 = $prop; + + return $this; + } + + public function getProp2() + { + return $this->prop2; + } + + public function setProp2($prop) + { + $this->prop2 = $prop; + + return $this; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f003/Foo.php b/vendor/myclabs/deep-copy/fixtures/f003/Foo.php new file mode 100644 index 0000000000..9cd76224a1 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f003/Foo.php @@ -0,0 +1,26 @@ +name = $name; + } + + public function getProp() + { + return $this->prop; + } + + public function setProp($prop) + { + $this->prop = $prop; + + return $this; + } +} \ No newline at end of file diff --git a/vendor/myclabs/deep-copy/fixtures/f004/UnclonableItem.php b/vendor/myclabs/deep-copy/fixtures/f004/UnclonableItem.php new file mode 100644 index 0000000000..82c6c67cdd --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f004/UnclonableItem.php @@ -0,0 +1,13 @@ +cloned = true; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f006/A.php b/vendor/myclabs/deep-copy/fixtures/f006/A.php new file mode 100644 index 0000000000..d9efb11667 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f006/A.php @@ -0,0 +1,26 @@ +aProp; + } + + public function setAProp($prop) + { + $this->aProp = $prop; + + return $this; + } + + public function __clone() + { + $this->cloned = true; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f006/B.php b/vendor/myclabs/deep-copy/fixtures/f006/B.php new file mode 100644 index 0000000000..1f80b3d44f --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f006/B.php @@ -0,0 +1,26 @@ +bProp; + } + + public function setBProp($prop) + { + $this->bProp = $prop; + + return $this; + } + + public function __clone() + { + $this->cloned = true; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f007/FooDateInterval.php b/vendor/myclabs/deep-copy/fixtures/f007/FooDateInterval.php new file mode 100644 index 0000000000..e16bc6aa67 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f007/FooDateInterval.php @@ -0,0 +1,15 @@ +cloned = true; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f007/FooDateTimeZone.php b/vendor/myclabs/deep-copy/fixtures/f007/FooDateTimeZone.php new file mode 100644 index 0000000000..6f4e61fe96 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f007/FooDateTimeZone.php @@ -0,0 +1,15 @@ +cloned = true; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f008/A.php b/vendor/myclabs/deep-copy/fixtures/f008/A.php new file mode 100644 index 0000000000..88471d013c --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f008/A.php @@ -0,0 +1,18 @@ +foo = $foo; + } + + public function getFoo() + { + return $this->foo; + } +} diff --git a/vendor/myclabs/deep-copy/fixtures/f008/B.php b/vendor/myclabs/deep-copy/fixtures/f008/B.php new file mode 100644 index 0000000000..6053092d75 --- /dev/null +++ b/vendor/myclabs/deep-copy/fixtures/f008/B.php @@ -0,0 +1,7 @@ + Filter, 'matcher' => Matcher] pairs. + */ + private $filters = []; + + /** + * Type Filters to apply. + * + * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + */ + private $typeFilters = []; + + /** + * @var bool + */ + private $skipUncloneable = false; + + /** + * @var bool + */ + private $useCloneMethod; + + /** + * @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used + * instead of the regular deep cloning. + */ + public function __construct($useCloneMethod = false) + { + $this->useCloneMethod = $useCloneMethod; + + $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class)); + $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class)); + } + + /** + * If enabled, will not throw an exception when coming across an uncloneable property. + * + * @param $skipUncloneable + * + * @return $this + */ + public function skipUncloneable($skipUncloneable = true) + { + $this->skipUncloneable = $skipUncloneable; + + return $this; + } + + /** + * Deep copies the given object. + * + * @param mixed $object + * + * @return mixed + */ + public function copy($object) + { + $this->hashMap = []; + + return $this->recursiveCopy($object); + } + + public function addFilter(Filter $filter, Matcher $matcher) + { + $this->filters[] = [ + 'matcher' => $matcher, + 'filter' => $filter, + ]; + } + + public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) + { + $this->typeFilters[] = [ + 'matcher' => $matcher, + 'filter' => $filter, + ]; + } + + private function recursiveCopy($var) + { + // Matches Type Filter + if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { + return $filter->apply($var); + } + + // Resource + if (is_resource($var)) { + return $var; + } + + // Array + if (is_array($var)) { + return $this->copyArray($var); + } + + // Scalar + if (! is_object($var)) { + return $var; + } + + // Object + return $this->copyObject($var); + } + + /** + * Copy an array + * @param array $array + * @return array + */ + private function copyArray(array $array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->recursiveCopy($value); + } + + return $array; + } + + /** + * Copies an object. + * + * @param object $object + * + * @throws CloneException + * + * @return object + */ + private function copyObject($object) + { + $objectHash = spl_object_hash($object); + + if (isset($this->hashMap[$objectHash])) { + return $this->hashMap[$objectHash]; + } + + $reflectedObject = new ReflectionObject($object); + $isCloneable = $reflectedObject->isCloneable(); + + if (false === $isCloneable) { + if ($this->skipUncloneable) { + $this->hashMap[$objectHash] = $object; + + return $object; + } + + throw new CloneException( + sprintf( + 'The class "%s" is not cloneable.', + $reflectedObject->getName() + ) + ); + } + + $newObject = clone $object; + $this->hashMap[$objectHash] = $newObject; + + if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { + return $newObject; + } + + if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) { + return $newObject; + } + + foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { + $this->copyObjectProperty($newObject, $property); + } + + return $newObject; + } + + private function copyObjectProperty($object, ReflectionProperty $property) + { + // Ignore static properties + if ($property->isStatic()) { + return; + } + + // Apply the filters + foreach ($this->filters as $item) { + /** @var Matcher $matcher */ + $matcher = $item['matcher']; + /** @var Filter $filter */ + $filter = $item['filter']; + + if ($matcher->matches($object, $property->getName())) { + $filter->apply( + $object, + $property->getName(), + function ($object) { + return $this->recursiveCopy($object); + } + ); + + // If a filter matches, we stop processing this property + return; + } + } + + $property->setAccessible(true); + $propertyValue = $property->getValue($object); + + // Copy the property + $property->setValue($object, $this->recursiveCopy($propertyValue)); + } + + /** + * Returns first filter that matches variable, `null` if no such filter found. + * + * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and + * 'matcher' with value of type {@see TypeMatcher} + * @param mixed $var + * + * @return TypeFilter|null + */ + private function getFirstMatchedTypeFilter(array $filterRecords, $var) + { + $matched = $this->first( + $filterRecords, + function (array $record) use ($var) { + /* @var TypeMatcher $matcher */ + $matcher = $record['matcher']; + + return $matcher->matches($var); + } + ); + + return isset($matched) ? $matched['filter'] : null; + } + + /** + * Returns first element that matches predicate, `null` if no such element found. + * + * @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + * @param callable $predicate Predicate arguments are: element. + * + * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher' + * with value of type {@see TypeMatcher} or `null`. + */ + private function first(array $elements, callable $predicate) + { + foreach ($elements as $element) { + if (call_user_func($predicate, $element)) { + return $element; + } + } + + return null; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php b/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php new file mode 100644 index 0000000000..c046706a27 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php @@ -0,0 +1,9 @@ +setAccessible(true); + $oldCollection = $reflectionProperty->getValue($object); + + $newCollection = $oldCollection->map( + function ($item) use ($objectCopier) { + return $objectCopier($item); + } + ); + + $reflectionProperty->setValue($object, $newCollection); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php new file mode 100644 index 0000000000..7b33fd5478 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php @@ -0,0 +1,28 @@ +setAccessible(true); + + $reflectionProperty->setValue($object, new ArrayCollection()); + } +} \ No newline at end of file diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php new file mode 100644 index 0000000000..8bee8f769a --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php @@ -0,0 +1,22 @@ +__load(); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php new file mode 100644 index 0000000000..85ba18ce11 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php @@ -0,0 +1,18 @@ +callback = $callable; + } + + /** + * Replaces the object property by the result of the callback called with the object property. + * + * {@inheritdoc} + */ + public function apply($object, $property, $objectCopier) + { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + $reflectionProperty->setAccessible(true); + + $value = call_user_func($this->callback, $reflectionProperty->getValue($object)); + + $reflectionProperty->setValue($object, $value); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php new file mode 100644 index 0000000000..bea86b8848 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php @@ -0,0 +1,24 @@ +setAccessible(true); + $reflectionProperty->setValue($object, null); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php new file mode 100644 index 0000000000..ec8856f503 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php @@ -0,0 +1,22 @@ +class = $class; + $this->property = $property; + } + + /** + * Matches a specific property of a specific class. + * + * {@inheritdoc} + */ + public function matches($object, $property) + { + return ($object instanceof $this->class) && $property == $this->property; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php new file mode 100644 index 0000000000..c8ec0d2bc1 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php @@ -0,0 +1,32 @@ +property = $property; + } + + /** + * Matches a property by its name. + * + * {@inheritdoc} + */ + public function matches($object, $property) + { + return $property == $this->property; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php new file mode 100644 index 0000000000..a6b0c0bc52 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php @@ -0,0 +1,46 @@ +propertyType = $propertyType; + } + + /** + * {@inheritdoc} + */ + public function matches($object, $property) + { + try { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + } catch (ReflectionException $exception) { + return false; + } + + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object) instanceof $this->propertyType; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php b/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php new file mode 100644 index 0000000000..742410cb2a --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php @@ -0,0 +1,78 @@ +getProperties() does not return private properties from ancestor classes. + * + * @author muratyaman@gmail.com + * @see http://php.net/manual/en/reflectionclass.getproperties.php + * + * @param ReflectionClass $ref + * + * @return ReflectionProperty[] + */ + public static function getProperties(ReflectionClass $ref) + { + $props = $ref->getProperties(); + $propsArr = array(); + + foreach ($props as $prop) { + $propertyName = $prop->getName(); + $propsArr[$propertyName] = $prop; + } + + if ($parentClass = $ref->getParentClass()) { + $parentPropsArr = self::getProperties($parentClass); + foreach ($propsArr as $key => $property) { + $parentPropsArr[$key] = $property; + } + + return $parentPropsArr; + } + + return $propsArr; + } + + /** + * Retrieves property by name from object and all its ancestors. + * + * @param object|string $object + * @param string $name + * + * @throws PropertyException + * @throws ReflectionException + * + * @return ReflectionProperty + */ + public static function getProperty($object, $name) + { + $reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object); + + if ($reflection->hasProperty($name)) { + return $reflection->getProperty($name); + } + + if ($parentClass = $reflection->getParentClass()) { + return self::getProperty($parentClass->getName(), $name); + } + + throw new PropertyException( + sprintf( + 'The class "%s" doesn\'t have a property with the given name: "%s".', + is_object($object) ? get_class($object) : $object, + $name + ) + ); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php new file mode 100644 index 0000000000..becd1cfffd --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php @@ -0,0 +1,33 @@ + $propertyValue) { + $copy->{$propertyName} = $propertyValue; + } + + return $copy; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php new file mode 100644 index 0000000000..164f8b8e26 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php @@ -0,0 +1,30 @@ +callback = $callable; + } + + /** + * {@inheritdoc} + */ + public function apply($element) + { + return call_user_func($this->callback, $element); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php new file mode 100644 index 0000000000..a5fbd7a2b4 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php @@ -0,0 +1,17 @@ +copier = $copier; + } + + /** + * {@inheritdoc} + */ + public function apply($element) + { + $newElement = clone $element; + + $copy = $this->createCopyClosure(); + + return $copy($newElement); + } + + private function createCopyClosure() + { + $copier = $this->copier; + + $copy = function (SplDoublyLinkedList $list) use ($copier) { + // Replace each element in the list with a deep copy of itself + for ($i = 1; $i <= $list->count(); $i++) { + $copy = $copier->recursiveCopy($list->shift()); + + $list->push($copy); + } + + return $list; + }; + + return Closure::bind($copy, null, DeepCopy::class); + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php new file mode 100644 index 0000000000..5785a7da9e --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php @@ -0,0 +1,13 @@ +type = $type; + } + + /** + * @param mixed $element + * + * @return boolean + */ + public function matches($element) + { + return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type; + } +} diff --git a/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php b/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php new file mode 100644 index 0000000000..55dcc92617 --- /dev/null +++ b/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php @@ -0,0 +1,20 @@ +copy($value); + } +} diff --git a/vendor/nikic/fast-route/.hhconfig b/vendor/nikic/fast-route/.hhconfig new file mode 100644 index 0000000000..0c2153ceb8 --- /dev/null +++ b/vendor/nikic/fast-route/.hhconfig @@ -0,0 +1 @@ +assume_php=false diff --git a/vendor/nikic/fast-route/.travis.yml b/vendor/nikic/fast-route/.travis.yml new file mode 100644 index 0000000000..10f83819e9 --- /dev/null +++ b/vendor/nikic/fast-route/.travis.yml @@ -0,0 +1,20 @@ +sudo: false +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm + +script: + - ./vendor/bin/phpunit + +before_install: + - travis_retry composer self-update + +install: + - composer install diff --git a/vendor/nikic/fast-route/FastRoute.hhi b/vendor/nikic/fast-route/FastRoute.hhi new file mode 100644 index 0000000000..8d507384d5 --- /dev/null +++ b/vendor/nikic/fast-route/FastRoute.hhi @@ -0,0 +1,126 @@ +; + } + + class RouteCollector { + public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator); + public function addRoute(mixed $httpMethod, string $route, mixed $handler): void; + public function getData(): array; + } + + class Route { + public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables); + public function matches(string $str): bool; + } + + interface DataGenerator { + public function addRoute(string $httpMethod, array $routeData, mixed $handler); + public function getData(): array; + } + + interface Dispatcher { + const int NOT_FOUND = 0; + const int FOUND = 1; + const int METHOD_NOT_ALLOWED = 2; + public function dispatch(string $httpMethod, string $uri): array; + } + + function simpleDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + ?'routeParser' => classname, + ?'dataGenerator' => classname, + ?'dispatcher' => classname, + ?'routeCollector' => classname, + ) $options = shape()): Dispatcher; + + function cachedDispatcher( + (function(RouteCollector): void) $routeDefinitionCallback, + shape( + ?'routeParser' => classname, + ?'dataGenerator' => classname, + ?'dispatcher' => classname, + ?'routeCollector' => classname, + ?'cacheDisabled' => bool, + ?'cacheFile' => string, + ) $options = shape()): Dispatcher; +} + +namespace FastRoute\DataGenerator { + abstract class RegexBasedAbstract implements \FastRoute\DataGenerator { + protected abstract function getApproxChunkSize(); + protected abstract function processChunk($regexToRoutesMap); + + public function addRoute(string $httpMethod, array $routeData, mixed $handler): void; + public function getData(): array; + } + + class CharCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupCountBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class GroupPosBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } + + class MarkBased extends RegexBasedAbstract { + protected function getApproxChunkSize(): int; + protected function processChunk(array $regexToRoutesMap): array; + } +} + +namespace FastRoute\Dispatcher { + abstract class RegexBasedAbstract implements \FastRoute\Dispatcher { + protected abstract function dispatchVariableRoute(array $routeData, string $uri): array; + + public function dispatch(string $httpMethod, string $uri): array; + } + + class GroupPosBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class GroupCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class CharCountBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } + + class MarkBased extends RegexBasedAbstract { + public function __construct(array $data); + protected function dispatchVariableRoute(array $routeData, string $uri): array; + } +} + +namespace FastRoute\RouteParser { + class Std implements \FastRoute\RouteParser { + const string VARIABLE_REGEX = <<<'REGEX' +\{ + \s* ([a-zA-Z][a-zA-Z0-9_]*) \s* + (?: + : \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*) + )? +\} +REGEX; + const string DEFAULT_DISPATCH_REGEX = '[^/]+'; + public function parse(string $route): array; + } +} diff --git a/vendor/nikic/fast-route/LICENSE b/vendor/nikic/fast-route/LICENSE new file mode 100644 index 0000000000..478e7641e9 --- /dev/null +++ b/vendor/nikic/fast-route/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2013 by Nikita Popov. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/nikic/fast-route/README.md b/vendor/nikic/fast-route/README.md new file mode 100644 index 0000000000..91bd4664e0 --- /dev/null +++ b/vendor/nikic/fast-route/README.md @@ -0,0 +1,313 @@ +FastRoute - Fast request router for PHP +======================================= + +This library provides a fast implementation of a regular expression based router. [Blog post explaining how the +implementation works and why it is fast.][blog_post] + +Install +------- + +To install with composer: + +```sh +composer require nikic/fast-route +``` + +Requires PHP 5.4 or newer. + +Usage +----- + +Here's a basic usage example: + +```php +addRoute('GET', '/users', 'get_all_users_handler'); + // {id} must be a number (\d+) + $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler'); + // The /{title} suffix is optional + $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler'); +}); + +// Fetch method and URI from somewhere +$httpMethod = $_SERVER['REQUEST_METHOD']; +$uri = $_SERVER['REQUEST_URI']; + +// Strip query string (?foo=bar) and decode URI +if (false !== $pos = strpos($uri, '?')) { + $uri = substr($uri, 0, $pos); +} +$uri = rawurldecode($uri); + +$routeInfo = $dispatcher->dispatch($httpMethod, $uri); +switch ($routeInfo[0]) { + case FastRoute\Dispatcher::NOT_FOUND: + // ... 404 Not Found + break; + case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $allowedMethods = $routeInfo[1]; + // ... 405 Method Not Allowed + break; + case FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2]; + // ... call $handler with $vars + break; +} +``` + +### Defining routes + +The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts +a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling +`addRoute()` on the collector instance: + +```php +$r->addRoute($method, $routePattern, $handler); +``` + +The `$method` is an uppercase HTTP method string for which a certain route should match. It +is possible to specify multiple valid methods using an array: + +```php +// These two calls +$r->addRoute('GET', '/test', 'handler'); +$r->addRoute('POST', '/test', 'handler'); +// Are equivalent to this one call +$r->addRoute(['GET', 'POST'], '/test', 'handler'); +``` + +By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo` +and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify +a custom pattern by writing `{bar:[0-9]+}`. Some examples: + +```php +// Matches /user/42, but not /user/xyz +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); + +// Matches /user/foobar, but not /user/foo/bar +$r->addRoute('GET', '/user/{name}', 'handler'); + +// Matches /user/foo/bar as well +$r->addRoute('GET', '/user/{name:.+}', 'handler'); +``` + +Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}` +is not a valid placeholder, because `()` is a capturing group. Instead you can use either +`{lang:en|de}` or `{lang:(?:en|de)}`. + +Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` +will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, +not in the middle of a route. + +```php +// This route +$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler'); +// Is equivalent to these two routes +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); +$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler'); + +// Multiple nested optional parts are possible as well +$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler'); + +// This route is NOT valid, because optional parts can only occur at the end +$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler'); +``` + +The `$handler` parameter does not necessarily have to be a callback, it could also be a controller +class name or any other kind of data you wish to associate with the route. FastRoute only tells you +which handler corresponds to your URI, how you interpret it is up to you. + +#### Shorcut methods for common request methods + +For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example: + +```php +$r->get('/get-route', 'get_handler'); +$r->post('/post-route', 'post_handler'); +``` + +Is equivalent to: + +```php +$r->addRoute('GET', '/get-route', 'get_handler'); +$r->addRoute('POST', '/post-route', 'post_handler'); +``` + +#### Route Groups + +Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix. + +For example, defining your routes as: + +```php +$r->addGroup('/admin', function (RouteCollector $r) { + $r->addRoute('GET', '/do-something', 'handler'); + $r->addRoute('GET', '/do-another-thing', 'handler'); + $r->addRoute('GET', '/do-something-else', 'handler'); +}); +``` + +Will have the same result as: + + ```php +$r->addRoute('GET', '/admin/do-something', 'handler'); +$r->addRoute('GET', '/admin/do-another-thing', 'handler'); +$r->addRoute('GET', '/admin/do-something-else', 'handler'); + ``` + +Nested groups are also supported, in which case the prefixes of all the nested groups are combined. + +### Caching + +The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless +caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated +routing data and construct the dispatcher from the cached information: + +```php +addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); +}, [ + 'cacheFile' => __DIR__ . '/route.cache', /* required */ + 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ +]); +``` + +The second parameter to the function is an options array, which can be used to specify the cache +file location, among other things. + +### Dispatching a URI + +A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method +accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them +appropriately) is your job - this library is not bound to the PHP web SAPIs. + +The `dispatch()` method returns an array whose first element contains a status code. It is one +of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the +method not allowed status the second array element contains a list of HTTP methods allowed for +the supplied URI. For example: + + [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] + +> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the +`Allow:` header to detail available methods for the requested resource. Applications using FastRoute +should use the second array element to add this header when relaying a 405 response. + +For the found status the second array element is the handler that was associated with the route +and the third array element is a dictionary of placeholder names to their values. For example: + + /* Routing against GET /user/nikic/42 */ + + [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] + +### Overriding the route parser and dispatcher + +The routing process makes use of three components: A route parser, a data generator and a +dispatcher. The three components adhere to the following interfaces: + +```php + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', +]); +``` + +The above options array corresponds to the defaults. By replacing `GroupCountBased` by +`GroupPosBased` you could switch to a different dispatching strategy. + +### A Note on HEAD Requests + +The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: + +> The methods GET and HEAD MUST be supported by all general-purpose servers + +To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an +available GET route for a given resource. The PHP web SAPI transparently removes the entity body +from HEAD responses so this behavior has no effect on the vast majority of users. + +However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST +NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is +*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. + +Finally, note that applications MAY always specify their own HEAD method route for a given +resource to bypass this behavior entirely. + +### Credits + +This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. + +A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. + + +[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" +[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html +[levi]: https://github.com/morrisonlevi +[rdlowrey]: https://github.com/rdlowrey diff --git a/vendor/nikic/fast-route/composer.json b/vendor/nikic/fast-route/composer.json new file mode 100644 index 0000000000..fb446a2abf --- /dev/null +++ b/vendor/nikic/fast-route/composer.json @@ -0,0 +1,24 @@ +{ + "name": "nikic/fast-route", + "description": "Fast request router for PHP", + "keywords": ["routing", "router"], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": ["src/functions.php"] + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + } +} diff --git a/vendor/nikic/fast-route/phpunit.xml b/vendor/nikic/fast-route/phpunit.xml new file mode 100644 index 0000000000..3c807b6acb --- /dev/null +++ b/vendor/nikic/fast-route/phpunit.xml @@ -0,0 +1,24 @@ + + + + + + ./test/ + + + + + + ./src/ + + + diff --git a/vendor/nikic/fast-route/psalm.xml b/vendor/nikic/fast-route/psalm.xml new file mode 100644 index 0000000000..0dca5d7e0c --- /dev/null +++ b/vendor/nikic/fast-route/psalm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/nikic/fast-route/src/BadRouteException.php b/vendor/nikic/fast-route/src/BadRouteException.php new file mode 100644 index 0000000000..62262ec66f --- /dev/null +++ b/vendor/nikic/fast-route/src/BadRouteException.php @@ -0,0 +1,7 @@ + $route) { + $suffixLen++; + $suffix .= "\t"; + + $regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})'; + $routeMap[$suffix] = [$route->handler, $route->variables]; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php b/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php new file mode 100644 index 0000000000..54d9a05e20 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php @@ -0,0 +1,30 @@ + $route) { + $numVariables = count($route->variables); + $numGroups = max($numGroups, $numVariables); + + $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables); + $routeMap[$numGroups + 1] = [$route->handler, $route->variables]; + + ++$numGroups; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php b/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php new file mode 100644 index 0000000000..fc4dc0aff6 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex; + $routeMap[$offset] = [$route->handler, $route->variables]; + + $offset += count($route->variables); + } + + $regex = '~^(?:' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php b/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php new file mode 100644 index 0000000000..0aebed9a16 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex . '(*MARK:' . $markName . ')'; + $routeMap[$markName] = [$route->handler, $route->variables]; + + ++$markName; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php b/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php new file mode 100644 index 0000000000..6457290599 --- /dev/null +++ b/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php @@ -0,0 +1,186 @@ +isStaticRoute($routeData)) { + $this->addStaticRoute($httpMethod, $routeData, $handler); + } else { + $this->addVariableRoute($httpMethod, $routeData, $handler); + } + } + + /** + * @return mixed[] + */ + public function getData() + { + if (empty($this->methodToRegexToRoutesMap)) { + return [$this->staticRoutes, []]; + } + + return [$this->staticRoutes, $this->generateVariableRouteData()]; + } + + /** + * @return mixed[] + */ + private function generateVariableRouteData() + { + $data = []; + foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) { + $chunkSize = $this->computeChunkSize(count($regexToRoutesMap)); + $chunks = array_chunk($regexToRoutesMap, $chunkSize, true); + $data[$method] = array_map([$this, 'processChunk'], $chunks); + } + return $data; + } + + /** + * @param int + * @return int + */ + private function computeChunkSize($count) + { + $numParts = max(1, round($count / $this->getApproxChunkSize())); + return (int) ceil($count / $numParts); + } + + /** + * @param mixed[] + * @return bool + */ + private function isStaticRoute($routeData) + { + return count($routeData) === 1 && is_string($routeData[0]); + } + + private function addStaticRoute($httpMethod, $routeData, $handler) + { + $routeStr = $routeData[0]; + + if (isset($this->staticRoutes[$httpMethod][$routeStr])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $routeStr, $httpMethod + )); + } + + if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { + foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { + if ($route->matches($routeStr)) { + throw new BadRouteException(sprintf( + 'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', + $routeStr, $route->regex, $httpMethod + )); + } + } + } + + $this->staticRoutes[$httpMethod][$routeStr] = $handler; + } + + private function addVariableRoute($httpMethod, $routeData, $handler) + { + list($regex, $variables) = $this->buildRegexForRoute($routeData); + + if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $regex, $httpMethod + )); + } + + $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( + $httpMethod, $handler, $regex, $variables + ); + } + + /** + * @param mixed[] + * @return mixed[] + */ + private function buildRegexForRoute($routeData) + { + $regex = ''; + $variables = []; + foreach ($routeData as $part) { + if (is_string($part)) { + $regex .= preg_quote($part, '~'); + continue; + } + + list($varName, $regexPart) = $part; + + if (isset($variables[$varName])) { + throw new BadRouteException(sprintf( + 'Cannot use the same placeholder "%s" twice', $varName + )); + } + + if ($this->regexHasCapturingGroups($regexPart)) { + throw new BadRouteException(sprintf( + 'Regex "%s" for parameter "%s" contains a capturing group', + $regexPart, $varName + )); + } + + $variables[$varName] = $varName; + $regex .= '(' . $regexPart . ')'; + } + + return [$regex, $variables]; + } + + /** + * @param string + * @return bool + */ + private function regexHasCapturingGroups($regex) + { + if (false === strpos($regex, '(')) { + // Needs to have at least a ( to contain a capturing group + return false; + } + + // Semi-accurate detection for capturing groups + return (bool) preg_match( + '~ + (?: + \(\?\( + | \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \] + | \\\\ . + ) (*SKIP)(*FAIL) | + \( + (?! + \? (?! <(?![!=]) | P< | \' ) + | \* + ) + ~x', + $regex + ); + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher.php b/vendor/nikic/fast-route/src/Dispatcher.php new file mode 100644 index 0000000000..4ae72a356b --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher.php @@ -0,0 +1,26 @@ + 'value', ...]] + * + * @param string $httpMethod + * @param string $uri + * + * @return array + */ + public function dispatch($httpMethod, $uri); +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php b/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php new file mode 100644 index 0000000000..ef1eec1345 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][end($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php b/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php new file mode 100644 index 0000000000..493e7a94f0 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][count($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php b/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php new file mode 100644 index 0000000000..498220ed6f --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php @@ -0,0 +1,33 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + // find first non-empty match + for ($i = 1; '' === $matches[$i]; ++$i); + + list($handler, $varNames) = $data['routeMap'][$i]; + + $vars = []; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[$i++]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php b/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php new file mode 100644 index 0000000000..22eb09ba57 --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][$matches['MARK']]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php b/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php new file mode 100644 index 0000000000..206e879f7b --- /dev/null +++ b/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php @@ -0,0 +1,88 @@ +staticRouteMap[$httpMethod][$uri])) { + $handler = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $handler, []]; + } + + $varRouteData = $this->variableRouteData; + if (isset($varRouteData[$httpMethod])) { + $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + if (isset($this->staticRouteMap['GET'][$uri])) { + $handler = $this->staticRouteMap['GET'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['GET'])) { + $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + } + + // If nothing else matches, try fallback routes + if (isset($this->staticRouteMap['*'][$uri])) { + $handler = $this->staticRouteMap['*'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['*'])) { + $result = $this->dispatchVariableRoute($varRouteData['*'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // Find allowed methods for this URI by matching against all other HTTP methods as well + $allowedMethods = []; + + foreach ($this->staticRouteMap as $method => $uriMap) { + if ($method !== $httpMethod && isset($uriMap[$uri])) { + $allowedMethods[] = $method; + } + } + + foreach ($varRouteData as $method => $routeData) { + if ($method === $httpMethod) { + continue; + } + + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $allowedMethods[] = $method; + } + } + + // If there are no allowed methods the route simply does not exist + if ($allowedMethods) { + return [self::METHOD_NOT_ALLOWED, $allowedMethods]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/vendor/nikic/fast-route/src/Route.php b/vendor/nikic/fast-route/src/Route.php new file mode 100644 index 0000000000..e1bf7dd972 --- /dev/null +++ b/vendor/nikic/fast-route/src/Route.php @@ -0,0 +1,47 @@ +httpMethod = $httpMethod; + $this->handler = $handler; + $this->regex = $regex; + $this->variables = $variables; + } + + /** + * Tests whether this route matches the given string. + * + * @param string $str + * + * @return bool + */ + public function matches($str) + { + $regex = '~^' . $this->regex . '$~'; + return (bool) preg_match($regex, $str); + } +} diff --git a/vendor/nikic/fast-route/src/RouteCollector.php b/vendor/nikic/fast-route/src/RouteCollector.php new file mode 100644 index 0000000000..c1c1762d8d --- /dev/null +++ b/vendor/nikic/fast-route/src/RouteCollector.php @@ -0,0 +1,152 @@ +routeParser = $routeParser; + $this->dataGenerator = $dataGenerator; + $this->currentGroupPrefix = ''; + } + + /** + * Adds a route to the collection. + * + * The syntax used in the $route string depends on the used route parser. + * + * @param string|string[] $httpMethod + * @param string $route + * @param mixed $handler + */ + public function addRoute($httpMethod, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $routeDatas = $this->routeParser->parse($route); + foreach ((array) $httpMethod as $method) { + foreach ($routeDatas as $routeData) { + $this->dataGenerator->addRoute($method, $routeData, $handler); + } + } + } + + /** + * Create a route group with a common prefix. + * + * All routes created in the passed callback will have the given group prefix prepended. + * + * @param string $prefix + * @param callable $callback + */ + public function addGroup($prefix, callable $callback) + { + $previousGroupPrefix = $this->currentGroupPrefix; + $this->currentGroupPrefix = $previousGroupPrefix . $prefix; + $callback($this); + $this->currentGroupPrefix = $previousGroupPrefix; + } + + /** + * Adds a GET route to the collection + * + * This is simply an alias of $this->addRoute('GET', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function get($route, $handler) + { + $this->addRoute('GET', $route, $handler); + } + + /** + * Adds a POST route to the collection + * + * This is simply an alias of $this->addRoute('POST', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function post($route, $handler) + { + $this->addRoute('POST', $route, $handler); + } + + /** + * Adds a PUT route to the collection + * + * This is simply an alias of $this->addRoute('PUT', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function put($route, $handler) + { + $this->addRoute('PUT', $route, $handler); + } + + /** + * Adds a DELETE route to the collection + * + * This is simply an alias of $this->addRoute('DELETE', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function delete($route, $handler) + { + $this->addRoute('DELETE', $route, $handler); + } + + /** + * Adds a PATCH route to the collection + * + * This is simply an alias of $this->addRoute('PATCH', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function patch($route, $handler) + { + $this->addRoute('PATCH', $route, $handler); + } + + /** + * Adds a HEAD route to the collection + * + * This is simply an alias of $this->addRoute('HEAD', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function head($route, $handler) + { + $this->addRoute('HEAD', $route, $handler); + } + + /** + * Returns the collected route data, as provided by the data generator. + * + * @return array + */ + public function getData() + { + return $this->dataGenerator->getData(); + } +} diff --git a/vendor/nikic/fast-route/src/RouteParser.php b/vendor/nikic/fast-route/src/RouteParser.php new file mode 100644 index 0000000000..6a7685cfed --- /dev/null +++ b/vendor/nikic/fast-route/src/RouteParser.php @@ -0,0 +1,37 @@ + $segment) { + if ($segment === '' && $n !== 0) { + throw new BadRouteException('Empty optional part'); + } + + $currentRoute .= $segment; + $routeDatas[] = $this->parsePlaceholders($currentRoute); + } + return $routeDatas; + } + + /** + * Parses a route string that does not contain optional segments. + * + * @param string + * @return mixed[] + */ + private function parsePlaceholders($route) + { + if (!preg_match_all( + '~' . self::VARIABLE_REGEX . '~x', $route, $matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER + )) { + return [$route]; + } + + $offset = 0; + $routeData = []; + foreach ($matches as $set) { + if ($set[0][1] > $offset) { + $routeData[] = substr($route, $offset, $set[0][1] - $offset); + } + $routeData[] = [ + $set[1][0], + isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX + ]; + $offset = $set[0][1] + strlen($set[0][0]); + } + + if ($offset !== strlen($route)) { + $routeData[] = substr($route, $offset); + } + + return $routeData; + } +} diff --git a/vendor/nikic/fast-route/src/bootstrap.php b/vendor/nikic/fast-route/src/bootstrap.php new file mode 100644 index 0000000000..0bce3a4207 --- /dev/null +++ b/vendor/nikic/fast-route/src/bootstrap.php @@ -0,0 +1,12 @@ + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + ]; + + /** @var RouteCollector $routeCollector */ + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + return new $options['dispatcher']($routeCollector->getData()); + } + + /** + * @param callable $routeDefinitionCallback + * @param array $options + * + * @return Dispatcher + */ + function cachedDispatcher(callable $routeDefinitionCallback, array $options = []) + { + $options += [ + 'routeParser' => 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + 'cacheDisabled' => false, + ]; + + if (!isset($options['cacheFile'])) { + throw new \LogicException('Must specify "cacheFile" option'); + } + + if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) { + $dispatchData = require $options['cacheFile']; + if (!is_array($dispatchData)) { + throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); + } + return new $options['dispatcher']($dispatchData); + } + + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + /** @var RouteCollector $routeCollector */ + $dispatchData = $routeCollector->getData(); + if (!$options['cacheDisabled']) { + file_put_contents( + $options['cacheFile'], + ' $this->getDataGeneratorClass(), + 'dispatcher' => $this->getDispatcherClass() + ]; + } + + /** + * @dataProvider provideFoundDispatchCases + */ + public function testFoundDispatches($method, $uri, $callback, $handler, $argDict) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $info = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::FOUND, $info[0]); + $this->assertSame($handler, $info[1]); + $this->assertSame($argDict, $info[2]); + } + + /** + * @dataProvider provideNotFoundDispatchCases + */ + public function testNotFoundDispatches($method, $uri, $callback) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertArrayNotHasKey(1, $routeInfo, + 'NOT_FOUND result must only contain a single element in the returned info array' + ); + $this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]); + } + + /** + * @dataProvider provideMethodNotAllowedDispatchCases + */ + public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods) + { + $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); + $routeInfo = $dispatcher->dispatch($method, $uri); + $this->assertArrayHasKey(1, $routeInfo, + 'METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1' + ); + + list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri); + $this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus); + $this->assertSame($availableMethods, $methodArray); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot use the same placeholder "test" twice + */ + public function testDuplicateVariableNameError() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET" + */ + public function testDuplicateVariableRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;) + $r->addRoute('GET', '/user/{name}', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET" + */ + public function testDuplicateStaticRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET" + */ + public function testShadowedStaticRoute() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/nikic', 'handler1'); + }, $this->generateDispatcherOptions()); + } + + /** + * @expectedException \FastRoute\BadRouteException + * @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group + */ + public function testCapturing() + { + \FastRoute\simpleDispatcher(function (RouteCollector $r) { + $r->addRoute('GET', '/{lang:(en|de)}', 'handler0'); + }, $this->generateDispatcherOptions()); + } + + public function provideFoundDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/resource/123/456'; + $handler = 'handler0'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/handler2'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $handler = 'handler2'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 3 --------------------------------------------------------------------------------------> + + // reuse $callback from #2 + + $method = 'GET'; + $uri = '/user/12345'; + $handler = 'handler1'; + $argDict = ['id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 4 --------------------------------------------------------------------------------------> + + // reuse $callback from #3 + + $method = 'GET'; + $uri = '/user/NaN'; + $handler = 'handler2'; + $argDict = ['name' => 'NaN']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse $callback from #4 + + $method = 'GET'; + $uri = '/user/rdlowrey/12345'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey', 'id' => '12345']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 6 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/12345/extension', 'handler1'); + $r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/user/12345.svg'; + $handler = 'handler2'; + $argDict = ['id' => '12345', 'extension' => 'svg']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 7 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/static0', 'handler2'); + $r->addRoute('GET', '/static1', 'handler3'); + $r->addRoute('HEAD', '/static1', 'handler4'); + }; + + $method = 'HEAD'; + $uri = '/user/rdlowrey'; + $handler = 'handler0'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 8 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #7 + + $method = 'HEAD'; + $uri = '/user/rdlowrey/1234'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey', 'id' => '1234']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 9 ----- Test GET method fallback on HEAD route miss ------------------------------------> + + // reuse $callback from #8 + + $method = 'HEAD'; + $uri = '/static0'; + $handler = 'handler2'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 10 ---- Test existing HEAD route used if available (no fallback) -----------------------> + + // reuse $callback from #9 + + $method = 'HEAD'; + $uri = '/static1'; + $handler = 'handler4'; + $argDict = []; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 11 ---- More specified routes are not shadowed by less specific of another method ------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 12 ---- Handler of more specific routes is used, if it occurs first --------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); + $r->addRoute('POST', '/user/{name}', 'handler2'); + }; + + $method = 'POST'; + $uri = '/user/rdlowrey'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 13 ---- Route with constant suffix -----------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}', 'handler0'); + $r->addRoute('GET', '/user/{name}/edit', 'handler1'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey/edit'; + $handler = 'handler1'; + $argDict = ['name' => 'rdlowrey']; + + $cases[] = [$method, $uri, $callback, $handler, $argDict]; + + // 14 ---- Handle multiple methods with the same handler ----------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $argDict = []; + $cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict]; + $cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict]; + + // 17 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']]; + + // 18 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '', 'handler0'); + }; + + $cases[] = ['GET', '', $callback, 'handler0', []]; + + // 19 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('HEAD', '/a/{foo}', 'handler0'); + $r->addRoute('GET', '/b/{foo}', 'handler1'); + }; + + $cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // 20 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('HEAD', '/a', 'handler0'); + $r->addRoute('GET', '/b', 'handler1'); + }; + + $cases[] = ['HEAD', '/b', $callback, 'handler1', []]; + + // 21 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/foo', 'handler0'); + $r->addRoute('HEAD', '/{bar}', 'handler1'); + }; + + $cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']]; + + // 22 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('*', '/{user}', 'handler1'); + $r->addRoute('GET', '/user', 'handler2'); + }; + + $cases[] = ['GET', '/user', $callback, 'handler2', []]; + + // 23 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('*', '/user', 'handler0'); + $r->addRoute('GET', '/user', 'handler1'); + }; + + $cases[] = ['POST', '/user', $callback, 'handler0', []]; + + // 24 ---- + + $cases[] = ['HEAD', '/user', $callback, 'handler1', []]; + + // 25 ---- + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/{bar}', 'handler0'); + $r->addRoute('*', '/foo', 'handler1'); + }; + + $cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']]; + + // 26 ---- + + $callback = function(RouteCollector $r) { + $r->addRoute('GET', '/user', 'handler0'); + $r->addRoute('*', '/{foo:.*}', 'handler1'); + }; + + $cases[] = ['POST', '/bar', $callback, 'handler1', ['foo' => 'bar']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideNotFoundDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 1 --------------------------------------------------------------------------------------> + + // reuse callback from #0 + $method = 'POST'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 2 --------------------------------------------------------------------------------------> + + // reuse callback from #1 + $method = 'PUT'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/handler0', 'handler0'); + $r->addRoute('GET', '/handler1', 'handler1'); + $r->addRoute('GET', '/handler2', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); + }; + + $method = 'GET'; + $uri = '/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 5 --------------------------------------------------------------------------------------> + + // reuse callback from #4 + $method = 'GET'; + $uri = '/user/rdlowrey/12345/not-found'; + + $cases[] = [$method, $uri, $callback]; + + // 6 --------------------------------------------------------------------------------------> + + // reuse callback from #5 + $method = 'HEAD'; + + $cases[] = [$method, $uri, $callback]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } + + public function provideMethodNotAllowedDispatchCases() + { + $cases = []; + + // 0 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + }; + + $method = 'POST'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 1 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/resource/123/456', 'handler0'); + $r->addRoute('POST', '/resource/123/456', 'handler1'); + $r->addRoute('PUT', '/resource/123/456', 'handler2'); + $r->addRoute('*', '/', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/resource/123/456'; + $allowedMethods = ['GET', 'POST', 'PUT']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 2 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1'); + $r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3'); + }; + + $method = 'DELETE'; + $uri = '/user/rdlowrey/42'; + $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 3 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user/{name}', 'handler1'); + $r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2'); + $r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3'); + }; + + $method = 'GET'; + $uri = '/user/rdlowrey'; + $allowedMethods = ['POST', 'PUT', 'PATCH']; + + $cases[] = [$method, $uri, $callback, $allowedMethods]; + + // 4 --------------------------------------------------------------------------------------> + + $callback = function (RouteCollector $r) { + $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); + $r->addRoute(['DELETE'], '/user', 'handlerDelete'); + $r->addRoute([], '/user', 'handlerNone'); + }; + + $cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']]; + + // 5 + + $callback = function (RouteCollector $r) { + $r->addRoute('POST', '/user.json', 'handler0'); + $r->addRoute('GET', '/{entity}.json', 'handler1'); + }; + + $cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']]; + + // x --------------------------------------------------------------------------------------> + + return $cases; + } +} diff --git a/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php b/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php new file mode 100644 index 0000000000..f821ef56b9 --- /dev/null +++ b/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php @@ -0,0 +1,16 @@ +markTestSkipped('PHP 5.6 required for MARK support'); + } + } + + protected function getDispatcherClass() + { + return 'FastRoute\\Dispatcher\\MarkBased'; + } + + protected function getDataGeneratorClass() + { + return 'FastRoute\\DataGenerator\\MarkBased'; + } +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php b/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php new file mode 100644 index 0000000000..b6fc53f770 --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/HackTypecheckerTest.php @@ -0,0 +1,44 @@ +markTestSkipped('HHVM only'); + } + if (!version_compare(HHVM_VERSION, '3.9.0', '>=')) { + $this->markTestSkipped('classname requires HHVM 3.9+'); + } + + // The typechecker recurses the whole tree, so it makes sure + // that everything in fixtures/ is valid when this runs. + + $output = []; + $exit_code = null; + exec( + 'hh_server --check ' . escapeshellarg(__DIR__ . '/../../') . ' 2>&1', + $output, + $exit_code + ); + if ($exit_code === self::SERVER_ALREADY_RUNNING_CODE) { + $this->assertTrue( + $recurse, + 'Typechecker still running after running hh_client stop' + ); + // Server already running - 3.10 => 3.11 regression: + // https://github.com/facebook/hhvm/issues/6646 + exec('hh_client stop 2>/dev/null'); + $this->testTypechecks(/* recurse = */ false); + return; + + } + $this->assertSame(0, $exit_code, implode("\n", $output)); + } +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php new file mode 100644 index 0000000000..05a9af231b --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/all_options.php @@ -0,0 +1,29 @@ + {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + ), + ); +} + +function all_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher( + $collector ==> {}, + shape( + 'routeParser' => \FastRoute\RouteParser\Std::class, + 'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => \FastRoute\RouteCollector::class, + 'cacheFile' => '/dev/null', + 'cacheDisabled' => false, + ), + ); +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php new file mode 100644 index 0000000000..61eb54190d --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/empty_options.php @@ -0,0 +1,11 @@ + {}, shape()); +} + +function empty_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}, shape()); +} diff --git a/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php new file mode 100644 index 0000000000..44b5422f58 --- /dev/null +++ b/vendor/nikic/fast-route/test/HackTypechecker/fixtures/no_options.php @@ -0,0 +1,11 @@ + {}); +} + +function no_options_cached(): \FastRoute\Dispatcher { + return \FastRoute\cachedDispatcher($collector ==> {}); +} diff --git a/vendor/nikic/fast-route/test/RouteCollectorTest.php b/vendor/nikic/fast-route/test/RouteCollectorTest.php new file mode 100644 index 0000000000..cc54407d65 --- /dev/null +++ b/vendor/nikic/fast-route/test/RouteCollectorTest.php @@ -0,0 +1,108 @@ +delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $expected = [ + ['DELETE', '/delete', 'delete'], + ['GET', '/get', 'get'], + ['HEAD', '/head', 'head'], + ['PATCH', '/patch', 'patch'], + ['POST', '/post', 'post'], + ['PUT', '/put', 'put'], + ]; + + $this->assertSame($expected, $r->routes); + } + + public function testGroups() + { + $r = new DummyRouteCollector(); + + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $r->addGroup('/group-one', function (DummyRouteCollector $r) { + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + + $r->addGroup('/group-two', function (DummyRouteCollector $r) { + $r->delete('/delete', 'delete'); + $r->get('/get', 'get'); + $r->head('/head', 'head'); + $r->patch('/patch', 'patch'); + $r->post('/post', 'post'); + $r->put('/put', 'put'); + }); + }); + + $r->addGroup('/admin', function (DummyRouteCollector $r) { + $r->get('-some-info', 'admin-some-info'); + }); + $r->addGroup('/admin-', function (DummyRouteCollector $r) { + $r->get('more-info', 'admin-more-info'); + }); + + $expected = [ + ['DELETE', '/delete', 'delete'], + ['GET', '/get', 'get'], + ['HEAD', '/head', 'head'], + ['PATCH', '/patch', 'patch'], + ['POST', '/post', 'post'], + ['PUT', '/put', 'put'], + ['DELETE', '/group-one/delete', 'delete'], + ['GET', '/group-one/get', 'get'], + ['HEAD', '/group-one/head', 'head'], + ['PATCH', '/group-one/patch', 'patch'], + ['POST', '/group-one/post', 'post'], + ['PUT', '/group-one/put', 'put'], + ['DELETE', '/group-one/group-two/delete', 'delete'], + ['GET', '/group-one/group-two/get', 'get'], + ['HEAD', '/group-one/group-two/head', 'head'], + ['PATCH', '/group-one/group-two/patch', 'patch'], + ['POST', '/group-one/group-two/post', 'post'], + ['PUT', '/group-one/group-two/put', 'put'], + ['GET', '/admin-some-info', 'admin-some-info'], + ['GET', '/admin-more-info', 'admin-more-info'], + ]; + + $this->assertSame($expected, $r->routes); + } +} + +class DummyRouteCollector extends RouteCollector +{ + public $routes = []; + + public function __construct() + { + } + + public function addRoute($method, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $this->routes[] = [$method, $route, $handler]; + } +} diff --git a/vendor/nikic/fast-route/test/RouteParser/StdTest.php b/vendor/nikic/fast-route/test/RouteParser/StdTest.php new file mode 100644 index 0000000000..e13e4de617 --- /dev/null +++ b/vendor/nikic/fast-route/test/RouteParser/StdTest.php @@ -0,0 +1,154 @@ +parse($routeString); + $this->assertSame($expectedRouteDatas, $routeDatas); + } + + /** @dataProvider provideTestParseError */ + public function testParseError($routeString, $expectedExceptionMessage) + { + $parser = new Std(); + $this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage); + $parser->parse($routeString); + } + + public function provideTestParse() + { + return [ + [ + '/test', + [ + ['/test'], + ] + ], + [ + '/test/{param}', + [ + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/te{ param }st', + [ + ['/te', ['param', '[^/]+'], 'st'] + ] + ], + [ + '/test/{param1}/test2/{param2}', + [ + ['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']] + ] + ], + [ + '/test/{param:\d+}', + [ + ['/test/', ['param', '\d+']] + ] + ], + [ + '/test/{ param : \d{1,9} }', + [ + ['/test/', ['param', '\d{1,9}']] + ] + ], + [ + '/test[opt]', + [ + ['/test'], + ['/testopt'], + ] + ], + [ + '/test[/{param}]', + [ + ['/test'], + ['/test/', ['param', '[^/]+']], + ] + ], + [ + '/{param}[opt]', + [ + ['/', ['param', '[^/]+']], + ['/', ['param', '[^/]+'], 'opt'] + ] + ], + [ + '/test[/{name}[/{id:[0-9]+}]]', + [ + ['/test'], + ['/test/', ['name', '[^/]+']], + ['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']], + ] + ], + [ + '', + [ + [''], + ] + ], + [ + '[test]', + [ + [''], + ['test'], + ] + ], + [ + '/{foo-bar}', + [ + ['/', ['foo-bar', '[^/]+']] + ] + ], + [ + '/{_foo:.*}', + [ + ['/', ['_foo', '.*']] + ] + ], + ]; + } + + public function provideTestParseError() + { + return [ + [ + '/test[opt', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[opt[opt2]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/testopt]', + "Number of opening '[' and closing ']' does not match" + ], + [ + '/test[]', + 'Empty optional part' + ], + [ + '/test[[opt]]', + 'Empty optional part' + ], + [ + '[[test]]', + 'Empty optional part' + ], + [ + '/test[/opt]/required', + 'Optional segments can only occur at the end of a route' + ], + ]; + } +} diff --git a/vendor/nikic/fast-route/test/bootstrap.php b/vendor/nikic/fast-route/test/bootstrap.php new file mode 100644 index 0000000000..3023f41e46 --- /dev/null +++ b/vendor/nikic/fast-route/test/bootstrap.php @@ -0,0 +1,11 @@ +=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." + }, + "autoload": { + "files": [ + "lib/random.php" + ] + } +} diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000000..eb50ebfcd6 --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000000..6a1d7f3006 --- /dev/null +++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/vendor/paragonie/random_compat/lib/byte_safe_strings.php new file mode 100644 index 0000000000..2a7335dd6b --- /dev/null +++ b/vendor/paragonie/random_compat/lib/byte_safe_strings.php @@ -0,0 +1,181 @@ + RandomCompat_strlen($binary_string)) { + return ''; + } + + return (string) mb_substr($binary_string, $start, $length, '8bit'); + } + + } else { + + /** + * substr() implementation that isn't brittle to mbstring.func_overload + * + * This version just uses the default substr() + * + * @param string $binary_string + * @param int $start + * @param int $length (optional) + * + * @throws TypeError + * + * @return string + */ + function RandomCompat_substr($binary_string, $start, $length = null) + { + if (!is_string($binary_string)) { + throw new TypeError( + 'RandomCompat_substr(): First argument should be a string' + ); + } + + if (!is_int($start)) { + throw new TypeError( + 'RandomCompat_substr(): Second argument should be an integer' + ); + } + + if ($length !== null) { + if (!is_int($length)) { + throw new TypeError( + 'RandomCompat_substr(): Third argument should be an integer, or omitted' + ); + } + + return (string) substr($binary_string, $start, $length); + } + + return (string) substr($binary_string, $start); + } + } +} diff --git a/vendor/paragonie/random_compat/lib/cast_to_int.php b/vendor/paragonie/random_compat/lib/cast_to_int.php new file mode 100644 index 0000000000..14b4b348fa --- /dev/null +++ b/vendor/paragonie/random_compat/lib/cast_to_int.php @@ -0,0 +1,75 @@ + operators might accidentally let a float + * through. + * + * @param int|float $number The number we want to convert to an int + * @param bool $fail_open Set to true to not throw an exception + * + * @return float|int + * @psalm-suppress InvalidReturnType + * + * @throws TypeError + */ + function RandomCompat_intval($number, $fail_open = false) + { + if (is_int($number) || is_float($number)) { + $number += 0; + } elseif (is_numeric($number)) { + $number += 0; + } + + if ( + is_float($number) + && + $number > ~PHP_INT_MAX + && + $number < PHP_INT_MAX + ) { + $number = (int) $number; + } + + if (is_int($number)) { + return (int) $number; + } elseif (!$fail_open) { + throw new TypeError( + 'Expected an integer.' + ); + } + return $number; + } +} diff --git a/vendor/paragonie/random_compat/lib/error_polyfill.php b/vendor/paragonie/random_compat/lib/error_polyfill.php new file mode 100644 index 0000000000..6d4a19ac7e --- /dev/null +++ b/vendor/paragonie/random_compat/lib/error_polyfill.php @@ -0,0 +1,49 @@ += 70000) { + return; +} + +if (!defined('RANDOM_COMPAT_READ_BUFFER')) { + define('RANDOM_COMPAT_READ_BUFFER', 8); +} + +$RandomCompatDIR = dirname(__FILE__); + +require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'byte_safe_strings.php'; +require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'cast_to_int.php'; +require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'error_polyfill.php'; + +if (!is_callable('random_bytes')) { + /** + * PHP 5.2.0 - 5.6.x way to implement random_bytes() + * + * We use conditional statements here to define the function in accordance + * to the operating environment. It's a micro-optimization. + * + * In order of preference: + * 1. Use libsodium if available. + * 2. fread() /dev/urandom if available (never on Windows) + * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) + * 4. COM('CAPICOM.Utilities.1')->GetRandom() + * + * See RATIONALE.md for our reasoning behind this particular order + */ + if (extension_loaded('libsodium')) { + // See random_bytes_libsodium.php + if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { + require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium.php'; + } elseif (method_exists('Sodium', 'randombytes_buf')) { + require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium_legacy.php'; + } + } + + /** + * Reading directly from /dev/urandom: + */ + if (DIRECTORY_SEPARATOR === '/') { + // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast + // way to exclude Windows. + $RandomCompatUrandom = true; + $RandomCompat_basedir = ini_get('open_basedir'); + + if (!empty($RandomCompat_basedir)) { + $RandomCompat_open_basedir = explode( + PATH_SEPARATOR, + strtolower($RandomCompat_basedir) + ); + $RandomCompatUrandom = (array() !== array_intersect( + array('/dev', '/dev/', '/dev/urandom'), + $RandomCompat_open_basedir + )); + $RandomCompat_open_basedir = null; + } + + if ( + !is_callable('random_bytes') + && + $RandomCompatUrandom + && + @is_readable('/dev/urandom') + ) { + // Error suppression on is_readable() in case of an open_basedir + // or safe_mode failure. All we care about is whether or not we + // can read it at this point. If the PHP environment is going to + // panic over trying to see if the file can be read in the first + // place, that is not helpful to us here. + + // See random_bytes_dev_urandom.php + require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_dev_urandom.php'; + } + // Unset variables after use + $RandomCompat_basedir = null; + } else { + $RandomCompatUrandom = false; + } + + /** + * mcrypt_create_iv() + * + * We only want to use mcypt_create_iv() if: + * + * - random_bytes() hasn't already been defined + * - the mcrypt extensions is loaded + * - One of these two conditions is true: + * - We're on Windows (DIRECTORY_SEPARATOR !== '/') + * - We're not on Windows and /dev/urandom is readabale + * (i.e. we're not in a chroot jail) + * - Special case: + * - If we're not on Windows, but the PHP version is between + * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will + * hang indefinitely. This is bad. + * - If we're on Windows, we want to use PHP >= 5.3.7 or else + * we get insufficient entropy errors. + */ + if ( + !is_callable('random_bytes') + && + // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. + (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) + && + // Prevent this code from hanging indefinitely on non-Windows; + // see https://bugs.php.net/bug.php?id=69833 + ( + DIRECTORY_SEPARATOR !== '/' || + (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) + ) + && + extension_loaded('mcrypt') + ) { + // See random_bytes_mcrypt.php + require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_mcrypt.php'; + } + $RandomCompatUrandom = null; + + /** + * This is a Windows-specific fallback, for when the mcrypt extension + * isn't loaded. + */ + if ( + !is_callable('random_bytes') + && + extension_loaded('com_dotnet') + && + class_exists('COM') + ) { + $RandomCompat_disabled_classes = preg_split( + '#\s*,\s*#', + strtolower(ini_get('disable_classes')) + ); + + if (!in_array('com', $RandomCompat_disabled_classes)) { + try { + $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); + if (method_exists($RandomCompatCOMtest, 'GetRandom')) { + // See random_bytes_com_dotnet.php + require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_com_dotnet.php'; + } + } catch (com_exception $e) { + // Don't try to use it. + } + } + $RandomCompat_disabled_classes = null; + $RandomCompatCOMtest = null; + } + + /** + * throw new Exception + */ + if (!is_callable('random_bytes')) { + /** + * We don't have any more options, so let's throw an exception right now + * and hope the developer won't let it fail silently. + * + * @param mixed $length + * @psalm-suppress MissingReturnType + * @psalm-suppress InvalidReturnType + * @throws Exception + * @return string + */ + function random_bytes($length) + { + unset($length); // Suppress "variable not used" warnings. + throw new Exception( + 'There is no suitable CSPRNG installed on your system' + ); + return ''; + } + } +} + +if (!is_callable('random_int')) { + require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_int.php'; +} + +$RandomCompatDIR = null; diff --git a/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php new file mode 100644 index 0000000000..2b83369ea0 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php @@ -0,0 +1,88 @@ +GetRandom($bytes, 0)); + if (RandomCompat_strlen($buf) >= $bytes) { + /** + * Return our random entropy buffer here: + */ + return RandomCompat_substr($buf, 0, $bytes); + } + ++$execCount; + } while ($execCount < $bytes); + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} \ No newline at end of file diff --git a/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php new file mode 100644 index 0000000000..ac36034af8 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php @@ -0,0 +1,167 @@ + 0); + + /** + * Is our result valid? + */ + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + /** + * Return our random entropy buffer here: + */ + return $buf; + } + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Error reading from source device' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php new file mode 100644 index 0000000000..1e017c1452 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php @@ -0,0 +1,88 @@ + 2147483647) { + $buf = ''; + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= \Sodium\randombytes_buf($n); + } + } else { + $buf = \Sodium\randombytes_buf($bytes); + } + + if ($buf !== false) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php new file mode 100644 index 0000000000..388da4dc9c --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php @@ -0,0 +1,92 @@ + 2147483647) { + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= Sodium::randombytes_buf((int) $n); + } + } else { + $buf .= Sodium::randombytes_buf((int) $bytes); + } + + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php new file mode 100644 index 0000000000..879450c910 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php @@ -0,0 +1,77 @@ + operators might accidentally let a float + * through. + */ + + try { + $min = RandomCompat_intval($min); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $min must be an integer' + ); + } + + try { + $max = RandomCompat_intval($max); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $max must be an integer' + ); + } + + /** + * Now that we've verified our weak typing system has given us an integer, + * let's validate the logic then we can move forward with generating random + * integers along a given range. + */ + if ($min > $max) { + throw new Error( + 'Minimum value must be less than or equal to the maximum value' + ); + } + + if ($max === $min) { + return (int) $min; + } + + /** + * Initialize variables to 0 + * + * We want to store: + * $bytes => the number of random bytes we need + * $mask => an integer bitmask (for use with the &) operator + * so we can minimize the number of discards + */ + $attempts = $bits = $bytes = $mask = $valueShift = 0; + + /** + * At this point, $range is a positive number greater than 0. It might + * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to + * a float and we will lose some precision. + */ + $range = $max - $min; + + /** + * Test for integer overflow: + */ + if (!is_int($range)) { + + /** + * Still safely calculate wider ranges. + * Provided by @CodesInChaos, @oittaa + * + * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 + * + * We use ~0 as a mask in this case because it generates all 1s + * + * @ref https://eval.in/400356 (32-bit) + * @ref http://3v4l.org/XX9r5 (64-bit) + */ + $bytes = PHP_INT_SIZE; + $mask = ~0; + + } else { + + /** + * $bits is effectively ceil(log($range, 2)) without dealing with + * type juggling + */ + while ($range > 0) { + if ($bits % 8 === 0) { + ++$bytes; + } + ++$bits; + $range >>= 1; + $mask = $mask << 1 | 1; + } + $valueShift = $min; + } + + $val = 0; + /** + * Now that we have our parameters set up, let's begin generating + * random integers until one falls between $min and $max + */ + do { + /** + * The rejection probability is at most 0.5, so this corresponds + * to a failure probability of 2^-128 for a working RNG + */ + if ($attempts > 128) { + throw new Exception( + 'random_int: RNG is broken - too many rejections' + ); + } + + /** + * Let's grab the necessary number of random bytes + */ + $randomByteString = random_bytes($bytes); + + /** + * Let's turn $randomByteString into an integer + * + * This uses bitwise operators (<< and |) to build an integer + * out of the values extracted from ord() + * + * Example: [9F] | [6D] | [32] | [0C] => + * 159 + 27904 + 3276800 + 201326592 => + * 204631455 + */ + $val &= 0; + for ($i = 0; $i < $bytes; ++$i) { + $val |= ord($randomByteString[$i]) << ($i * 8); + } + + /** + * Apply mask + */ + $val &= $mask; + $val += $valueShift; + + ++$attempts; + /** + * If $val overflows to a floating point number, + * ... or is larger than $max, + * ... or smaller than $min, + * then try again. + */ + } while (!is_int($val) || $val > $max || $val < $min); + + return (int) $val; + } +} diff --git a/vendor/paragonie/random_compat/other/build_phar.php b/vendor/paragonie/random_compat/other/build_phar.php new file mode 100644 index 0000000000..70ef4b2ed8 --- /dev/null +++ b/vendor/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000000..d71d1b818c --- /dev/null +++ b/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/vendor/phpdocumentor/reflection-common/.travis.yml b/vendor/phpdocumentor/reflection-common/.travis.yml new file mode 100644 index 0000000000..958ecf864b --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/.travis.yml @@ -0,0 +1,35 @@ +language: php +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - hhvm + - nightly + +matrix: + allow_failures: + - php: + - hhvm + - nightly + +cache: + directories: + - $HOME/.composer/cache + +script: + - vendor/bin/phpunit --coverage-clover=coverage.clover -v + - composer update --no-interaction --prefer-source + - vendor/bin/phpunit -v + +before_script: + - composer install --no-interaction + +after_script: + - if [ $TRAVIS_PHP_VERSION = '5.6' ]; then wget https://scrutinizer-ci.com/ocular.phar; php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi + +notifications: + irc: "irc.freenode.org#phpdocumentor" + email: + - me@mikevanriel.com + - ashnazg@php.net diff --git a/vendor/phpdocumentor/reflection-common/LICENSE b/vendor/phpdocumentor/reflection-common/LICENSE new file mode 100644 index 0000000000..ed6926c1ee --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 phpDocumentor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/phpdocumentor/reflection-common/README.md b/vendor/phpdocumentor/reflection-common/README.md new file mode 100644 index 0000000000..68a80c82f1 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/README.md @@ -0,0 +1,2 @@ +# ReflectionCommon +[![Build Status](https://travis-ci.org/phpDocumentor/ReflectionCommon.svg?branch=master)](https://travis-ci.org/phpDocumentor/ReflectionCommon) diff --git a/vendor/phpdocumentor/reflection-common/composer.json b/vendor/phpdocumentor/reflection-common/composer.json new file mode 100644 index 0000000000..90eee0f083 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/composer.json @@ -0,0 +1,29 @@ +{ + "name": "phpdocumentor/reflection-common", + "keywords": ["phpdoc", "phpDocumentor", "reflection", "static analysis", "FQSEN"], + "homepage": "http://www.phpdoc.org", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "license": "MIT", + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "require": { + "php": ">=5.5" + }, + "autoload" : { + "psr-4" : { + "phpDocumentor\\Reflection\\": ["src"] + } + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/phpdocumentor/reflection-common/src/Element.php b/vendor/phpdocumentor/reflection-common/src/Element.php new file mode 100644 index 0000000000..712e30e8f7 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/src/Element.php @@ -0,0 +1,32 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +/** + * Interface for files processed by the ProjectFactory + */ +interface File +{ + /** + * Returns the content of the file as a string. + * + * @return string + */ + public function getContents(); + + /** + * Returns md5 hash of the file. + * + * @return string + */ + public function md5(); + + /** + * Returns an relative path to the file. + * + * @return string + */ + public function path(); +} diff --git a/vendor/phpdocumentor/reflection-common/src/Fqsen.php b/vendor/phpdocumentor/reflection-common/src/Fqsen.php new file mode 100644 index 0000000000..ce88d03ffc --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/src/Fqsen.php @@ -0,0 +1,82 @@ +fqsen = $fqsen; + + if (isset($matches[2])) { + $this->name = $matches[2]; + } else { + $matches = explode('\\', $fqsen); + $this->name = trim(end($matches), '()'); + } + } + + /** + * converts this class to string. + * + * @return string + */ + public function __toString() + { + return $this->fqsen; + } + + /** + * Returns the name of the element without path. + * + * @return string + */ + public function getName() + { + return $this->name; + } +} diff --git a/vendor/phpdocumentor/reflection-common/src/Location.php b/vendor/phpdocumentor/reflection-common/src/Location.php new file mode 100644 index 0000000000..5760321949 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/src/Location.php @@ -0,0 +1,57 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +/** + * The location where an element occurs within a file. + */ +final class Location +{ + /** @var int */ + private $lineNumber = 0; + + /** @var int */ + private $columnNumber = 0; + + /** + * Initializes the location for an element using its line number in the file and optionally the column number. + * + * @param int $lineNumber + * @param int $columnNumber + */ + public function __construct($lineNumber, $columnNumber = 0) + { + $this->lineNumber = $lineNumber; + $this->columnNumber = $columnNumber; + } + + /** + * Returns the line number that is covered by this location. + * + * @return integer + */ + public function getLineNumber() + { + return $this->lineNumber; + } + + /** + * Returns the column number (character position on a line) for this location object. + * + * @return integer + */ + public function getColumnNumber() + { + return $this->columnNumber; + } +} diff --git a/vendor/phpdocumentor/reflection-common/src/Project.php b/vendor/phpdocumentor/reflection-common/src/Project.php new file mode 100644 index 0000000000..3ed1e39343 --- /dev/null +++ b/vendor/phpdocumentor/reflection-common/src/Project.php @@ -0,0 +1,25 @@ +create($docComment); +``` + +The `create` method will yield an object of type `\phpDocumentor\Reflection\DocBlock` +whose methods can be queried: + +```php +// Contains the summary for this DocBlock +$summary = $docblock->getSummary(); + +// Contains \phpDocumentor\Reflection\DocBlock\Description object +$description = $docblock->getDescription(); + +// You can either cast it to string +$description = (string) $docblock->getDescription(); + +// Or use the render method to get a string representation of the Description. +$description = $docblock->getDescription()->render(); +``` + +> For more examples it would be best to review the scripts in the [`/examples` folder](/examples). diff --git a/vendor/phpdocumentor/reflection-docblock/composer.json b/vendor/phpdocumentor/reflection-docblock/composer.json new file mode 100644 index 0000000000..e3dc38a9e2 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/composer.json @@ -0,0 +1,34 @@ +{ + "name": "phpdocumentor/reflection-docblock", + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "autoload": { + "psr-4": {"phpDocumentor\\Reflection\\": ["src/"]} + }, + "autoload-dev": { + "psr-4": {"phpDocumentor\\Reflection\\": ["tests/unit"]} + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4", + "doctrine/instantiator": "~1.0.5" + }, + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/easy-coding-standard.neon b/vendor/phpdocumentor/reflection-docblock/easy-coding-standard.neon new file mode 100644 index 0000000000..7c2ba6e2db --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/easy-coding-standard.neon @@ -0,0 +1,31 @@ +includes: + - temp/ecs/config/clean-code.neon + - temp/ecs/config/psr2-checkers.neon + - temp/ecs/config/spaces.neon + - temp/ecs/config/common.neon + +checkers: + PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: + spacing: one + +parameters: + exclude_checkers: + # from temp/ecs/config/common.neon + - PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer + - PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer + - PhpCsFixer\Fixer\ControlStructure\YodaStyleFixer + # from temp/ecs/config/spaces.neon + - PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer + + skip: + SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: + # WIP code + - src/DocBlock/StandardTagFactory.php + PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\EmptyStatementSniff: + # WIP code + - src/DocBlock/StandardTagFactory.php + PHP_CodeSniffer\Standards\Squiz\Sniffs\Classes\ValidClassNameSniff: + - src/DocBlock/Tags/Return_.php + - src/DocBlock/Tags/Var_.php + PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff: + - */tests/** diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php new file mode 100644 index 0000000000..46605b7848 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock.php @@ -0,0 +1,236 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +use phpDocumentor\Reflection\DocBlock\Tag; +use Webmozart\Assert\Assert; + +final class DocBlock +{ + /** @var string The opening line for this docblock. */ + private $summary = ''; + + /** @var DocBlock\Description The actual description for this docblock. */ + private $description = null; + + /** @var Tag[] An array containing all the tags in this docblock; except inline. */ + private $tags = []; + + /** @var Types\Context Information about the context of this DocBlock. */ + private $context = null; + + /** @var Location Information about the location of this DocBlock. */ + private $location = null; + + /** @var bool Is this DocBlock (the start of) a template? */ + private $isTemplateStart = false; + + /** @var bool Does this DocBlock signify the end of a DocBlock template? */ + private $isTemplateEnd = false; + + /** + * @param string $summary + * @param DocBlock\Description $description + * @param DocBlock\Tag[] $tags + * @param Types\Context $context The context in which the DocBlock occurs. + * @param Location $location The location within the file that this DocBlock occurs in. + * @param bool $isTemplateStart + * @param bool $isTemplateEnd + */ + public function __construct( + $summary = '', + DocBlock\Description $description = null, + array $tags = [], + Types\Context $context = null, + Location $location = null, + $isTemplateStart = false, + $isTemplateEnd = false + ) { + Assert::string($summary); + Assert::boolean($isTemplateStart); + Assert::boolean($isTemplateEnd); + Assert::allIsInstanceOf($tags, Tag::class); + + $this->summary = $summary; + $this->description = $description ?: new DocBlock\Description(''); + foreach ($tags as $tag) { + $this->addTag($tag); + } + + $this->context = $context; + $this->location = $location; + + $this->isTemplateEnd = $isTemplateEnd; + $this->isTemplateStart = $isTemplateStart; + } + + /** + * @return string + */ + public function getSummary() + { + return $this->summary; + } + + /** + * @return DocBlock\Description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Returns the current context. + * + * @return Types\Context + */ + public function getContext() + { + return $this->context; + } + + /** + * Returns the current location. + * + * @return Location + */ + public function getLocation() + { + return $this->location; + } + + /** + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. + * + * @return boolean + */ + public function isTemplateStart() + { + return $this->isTemplateStart; + } + + /** + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. + * + * @return boolean + */ + public function isTemplateEnd() + { + return $this->isTemplateEnd; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * Returns an array of tags matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return Tag[] + */ + public function getTagsByName($name) + { + Assert::string($name); + + $result = []; + + /** @var Tag $tag */ + foreach ($this->getTags() as $tag) { + if ($tag->getName() !== $name) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Checks if a tag of a certain type is present in this DocBlock. + * + * @param string $name Tag name to check for. + * + * @return bool + */ + public function hasTag($name) + { + Assert::string($name); + + /** @var Tag $tag */ + foreach ($this->getTags() as $tag) { + if ($tag->getName() === $name) { + return true; + } + } + + return false; + } + + /** + * Remove a tag from this DocBlock. + * + * @param Tag $tag The tag to remove. + * + * @return void + */ + public function removeTag(Tag $tagToRemove) + { + foreach ($this->tags as $key => $tag) { + if ($tag === $tagToRemove) { + unset($this->tags[$key]); + break; + } + } + } + + /** + * Adds a tag to this DocBlock. + * + * @param Tag $tag The tag to add. + * + * @return void + */ + private function addTag(Tag $tag) + { + $this->tags[] = $tag; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php new file mode 100644 index 0000000000..25a79e0078 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php @@ -0,0 +1,114 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\DocBlock\Tags\Formatter; +use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter; +use Webmozart\Assert\Assert; + +/** + * Object representing to description for a DocBlock. + * + * A Description object can consist of plain text but can also include tags. A Description Formatter can then combine + * a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete + * description text using the format that you would prefer. + * + * Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is + * thus recommended to use that to create a Description object, like this: + * + * $description = $descriptionFactory->create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Description\Formatter for the formatting of the body and tags. + */ +class Description +{ + /** @var string */ + private $bodyTemplate; + + /** @var Tag[] */ + private $tags; + + /** + * Initializes a Description with its body (template) and a listing of the tags used in the body template. + * + * @param string $bodyTemplate + * @param Tag[] $tags + */ + public function __construct($bodyTemplate, array $tags = []) + { + Assert::string($bodyTemplate); + + $this->bodyTemplate = $bodyTemplate; + $this->tags = $tags; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. + * + * @param Formatter|null $formatter + * + * @return string + */ + public function render(Formatter $formatter = null) + { + if ($formatter === null) { + $formatter = new PassthroughFormatter(); + } + + $tags = []; + foreach ($this->tags as $tag) { + $tags[] = '{' . $formatter->format($tag) . '}'; + } + + return vsprintf($this->bodyTemplate, $tags); + } + + /** + * Returns a plain string representation of this description. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php new file mode 100644 index 0000000000..48f9c21951 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php @@ -0,0 +1,191 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\Types\Context as TypeContext; + +/** + * Creates a new Description object given a body of text. + * + * Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their + * body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the + * Description object's `render` method. + * + * In addition to the above does a Description support two types of escape sequences: + * + * 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}` + * 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description + * of an inline tag. + * + * If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning + * of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping + * over unexpected spaces as can be observed with tag descriptions. + */ +class DescriptionFactory +{ + /** @var TagFactory */ + private $tagFactory; + + /** + * Initializes this factory with the means to construct (inline) tags. + * + * @param TagFactory $tagFactory + */ + public function __construct(TagFactory $tagFactory) + { + $this->tagFactory = $tagFactory; + } + + /** + * Returns the parsed text of this description. + * + * @param string $contents + * @param TypeContext $context + * + * @return Description + */ + public function create($contents, TypeContext $context = null) + { + list($text, $tags) = $this->parse($this->lex($contents), $context); + + return new Description($text, $tags); + } + + /** + * Strips the contents from superfluous whitespace and splits the description into a series of tokens. + * + * @param string $contents + * + * @return string[] A series of tokens of which the description text is composed. + */ + private function lex($contents) + { + $contents = $this->removeSuperfluousStartingWhitespace($contents); + + // performance optimalization; if there is no inline tag, don't bother splitting it up. + if (strpos($contents, '{@') === false) { + return [$contents]; + } + + return preg_split( + '/\{ + # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally. + (?!@\}) + # We want to capture the whole tag line, but without the inline tag delimiters. + (\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. + \{(?1)?\} + | + # Make sure we match hanging "{". + \{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. We use "*" since there may not be any + # nested inline tags. + ) + \}/Sux', + $contents, + null, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * Parses the stream of tokens in to a new set of tokens containing Tags. + * + * @param string[] $tokens + * @param TypeContext $context + * + * @return string[]|Tag[] + */ + private function parse($tokens, TypeContext $context) + { + $count = count($tokens); + $tagCount = 0; + $tags = []; + + for ($i = 1; $i < $count; $i += 2) { + $tags[] = $this->tagFactory->create($tokens[$i], $context); + $tokens[$i] = '%' . ++$tagCount . '$s'; + } + + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //"%" is escaped to "%%" because of vsprintf. + //See unit tests for examples. + for ($i = 0; $i < $count; $i += 2) { + $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); + } + + return [implode('', $tokens), $tags]; + } + + /** + * Removes the superfluous from a multi-line description. + * + * When a description has more than one line then it can happen that the second and subsequent lines have an + * additional indentation. This is commonly in use with tags like this: + * + * {@}since 1.1.0 This is an example + * description where we have an + * indentation in the second and + * subsequent lines. + * + * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent + * lines and this may cause rendering issues when, for example, using a Markdown converter. + * + * @param string $contents + * + * @return string + */ + private function removeSuperfluousStartingWhitespace($contents) + { + $lines = explode("\n", $contents); + + // if there is only one line then we don't have lines with superfluous whitespace and + // can use the contents as-is + if (count($lines) <= 1) { + return $contents; + } + + // determine how many whitespace characters need to be stripped + $startingSpaceCount = 9999999; + for ($i = 1; $i < count($lines); $i++) { + // lines with a no length do not count as they are not indented at all + if (strlen(trim($lines[$i])) === 0) { + continue; + } + + // determine the number of prefixing spaces by checking the difference in line length before and after + // an ltrim + $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); + } + + // strip the number of spaces from each line + if ($startingSpaceCount > 0) { + for ($i = 1; $i < count($lines); $i++) { + $lines[$i] = substr($lines[$i], $startingSpaceCount); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php new file mode 100644 index 0000000000..571ed74990 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php @@ -0,0 +1,170 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\DocBlock\Tags\Example; + +/** + * Class used to find an example file's location based on a given ExampleDescriptor. + */ +class ExampleFinder +{ + /** @var string */ + private $sourceDirectory = ''; + + /** @var string[] */ + private $exampleDirectories = []; + + /** + * Attempts to find the example contents for the given descriptor. + * + * @param Example $example + * + * @return string + */ + public function find(Example $example) + { + $filename = $example->getFilePath(); + + $file = $this->getExampleFileContents($filename); + if (!$file) { + return "** File not found : {$filename} **"; + } + + return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); + } + + /** + * Registers the project's root directory where an 'examples' folder can be expected. + * + * @param string $directory + * + * @return void + */ + public function setSourceDirectory($directory = '') + { + $this->sourceDirectory = $directory; + } + + /** + * Returns the project's root directory where an 'examples' folder can be expected. + * + * @return string + */ + public function getSourceDirectory() + { + return $this->sourceDirectory; + } + + /** + * Registers a series of directories that may contain examples. + * + * @param string[] $directories + */ + public function setExampleDirectories(array $directories) + { + $this->exampleDirectories = $directories; + } + + /** + * Returns a series of directories that may contain examples. + * + * @return string[] + */ + public function getExampleDirectories() + { + return $this->exampleDirectories; + } + + /** + * Attempts to find the requested example file and returns its contents or null if no file was found. + * + * This method will try several methods in search of the given example file, the first one it encounters is + * returned: + * + * 1. Iterates through all examples folders for the given filename + * 2. Checks the source folder for the given filename + * 3. Checks the 'examples' folder in the current working directory for examples + * 4. Checks the path relative to the current working directory for the given filename + * + * @param string $filename + * + * @return string|null + */ + private function getExampleFileContents($filename) + { + $normalizedPath = null; + + foreach ($this->exampleDirectories as $directory) { + $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); + if (is_readable($exampleFileFromConfig)) { + $normalizedPath = $exampleFileFromConfig; + break; + } + } + + if (!$normalizedPath) { + if (is_readable($this->getExamplePathFromSource($filename))) { + $normalizedPath = $this->getExamplePathFromSource($filename); + } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { + $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); + } elseif (is_readable($filename)) { + $normalizedPath = $filename; + } + } + + return $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : null; + } + + /** + * Get example filepath based on the example directory inside your project. + * + * @param string $file + * + * @return string + */ + private function getExamplePathFromExampleDirectory($file) + { + return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; + } + + /** + * Returns a path to the example file in the given directory.. + * + * @param string $directory + * @param string $file + * + * @return string + */ + private function constructExamplePath($directory, $file) + { + return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; + } + + /** + * Get example filepath based on sourcecode. + * + * @param string $file + * + * @return string + */ + private function getExamplePathFromSource($file) + { + return sprintf( + '%s%s%s', + trim($this->getSourceDirectory(), '\\/'), + DIRECTORY_SEPARATOR, + trim($file, '"') + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php new file mode 100644 index 0000000000..0f355f588d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php @@ -0,0 +1,155 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\DocBlock; +use Webmozart\Assert\Assert; + +/** + * Converts a DocBlock back from an object to a complete DocComment including Asterisks. + */ +class Serializer +{ + /** @var string The string to indent the comment with. */ + protected $indentString = ' '; + + /** @var int The number of times the indent string is repeated. */ + protected $indent = 0; + + /** @var bool Whether to indent the first line with the given indent amount and string. */ + protected $isFirstLineIndented = true; + + /** @var int|null The max length of a line. */ + protected $lineLength = null; + + /** @var DocBlock\Tags\Formatter A custom tag formatter. */ + protected $tagFormatter = null; + + /** + * Create a Serializer instance. + * + * @param int $indent The number of times the indent string is repeated. + * @param string $indentString The string to indent the comment with. + * @param bool $indentFirstLine Whether to indent the first line. + * @param int|null $lineLength The max length of a line or NULL to disable line wrapping. + * @param DocBlock\Tags\Formatter $tagFormatter A custom tag formatter, defaults to PassthroughFormatter. + */ + public function __construct($indent = 0, $indentString = ' ', $indentFirstLine = true, $lineLength = null, $tagFormatter = null) + { + Assert::integer($indent); + Assert::string($indentString); + Assert::boolean($indentFirstLine); + Assert::nullOrInteger($lineLength); + Assert::nullOrIsInstanceOf($tagFormatter, 'phpDocumentor\Reflection\DocBlock\Tags\Formatter'); + + $this->indent = $indent; + $this->indentString = $indentString; + $this->isFirstLineIndented = $indentFirstLine; + $this->lineLength = $lineLength; + $this->tagFormatter = $tagFormatter ?: new DocBlock\Tags\Formatter\PassthroughFormatter(); + } + + /** + * Generate a DocBlock comment. + * + * @param DocBlock $docblock The DocBlock to serialize. + * + * @return string The serialized doc block. + */ + public function getDocComment(DocBlock $docblock) + { + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + // 3 === strlen(' * ') + $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null; + + $text = $this->removeTrailingSpaces( + $indent, + $this->addAsterisksForEachLine( + $indent, + $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength) + ) + ); + + $comment = "{$firstIndent}/**\n"; + if ($text) { + $comment .= "{$indent} * {$text}\n"; + $comment .= "{$indent} *\n"; + } + + $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); + $comment .= $indent . ' */'; + + return $comment; + } + + /** + * @param $indent + * @param $text + * @return mixed + */ + private function removeTrailingSpaces($indent, $text) + { + return str_replace("\n{$indent} * \n", "\n{$indent} *\n", $text); + } + + /** + * @param $indent + * @param $text + * @return mixed + */ + private function addAsterisksForEachLine($indent, $text) + { + return str_replace("\n", "\n{$indent} * ", $text); + } + + /** + * @param DocBlock $docblock + * @param $wrapLength + * @return string + */ + private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, $wrapLength) + { + $text = $docblock->getSummary() . ((string)$docblock->getDescription() ? "\n\n" . $docblock->getDescription() + : ''); + if ($wrapLength !== null) { + $text = wordwrap($text, $wrapLength); + return $text; + } + + return $text; + } + + /** + * @param DocBlock $docblock + * @param $wrapLength + * @param $indent + * @param $comment + * @return string + */ + private function addTagBlock(DocBlock $docblock, $wrapLength, $indent, $comment) + { + foreach ($docblock->getTags() as $tag) { + $tagText = $this->tagFormatter->format($tag); + if ($wrapLength !== null) { + $tagText = wordwrap($tagText, $wrapLength); + } + + $tagText = str_replace("\n", "\n{$indent} * ", $tagText); + + $comment .= "{$indent} * {$tagText}\n"; + } + + return $comment; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php new file mode 100644 index 0000000000..5a8143cf9b --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php @@ -0,0 +1,319 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod; +use phpDocumentor\Reflection\DocBlock\Tags\Generic; +use phpDocumentor\Reflection\FqsenResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Creates a Tag object given the contents of a tag. + * + * This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create` + * factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can + * pass the dependencies that you need to construct a tag object. + * + * > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise + * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to + * > verify that a dependency is actually passed. + * + * This Factory also features a Service Locator component that is used to pass the right dependencies to the + * `create` method of a tag; each dependency should be registered as a service or as a parameter. + * + * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass + * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. + */ +final class StandardTagFactory implements TagFactory +{ + /** PCRE regular expression matching a tag name. */ + const REGEX_TAGNAME = '[\w\-\_\\\\]+'; + + /** + * @var string[] An array with a tag as a key, and an FQCN to a class that handles it as an array value. + */ + private $tagHandlerMappings = [ + 'author' => '\phpDocumentor\Reflection\DocBlock\Tags\Author', + 'covers' => '\phpDocumentor\Reflection\DocBlock\Tags\Covers', + 'deprecated' => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated', + // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', + 'link' => '\phpDocumentor\Reflection\DocBlock\Tags\Link', + 'method' => '\phpDocumentor\Reflection\DocBlock\Tags\Method', + 'param' => '\phpDocumentor\Reflection\DocBlock\Tags\Param', + 'property-read' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead', + 'property' => '\phpDocumentor\Reflection\DocBlock\Tags\Property', + 'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite', + 'return' => '\phpDocumentor\Reflection\DocBlock\Tags\Return_', + 'see' => '\phpDocumentor\Reflection\DocBlock\Tags\See', + 'since' => '\phpDocumentor\Reflection\DocBlock\Tags\Since', + 'source' => '\phpDocumentor\Reflection\DocBlock\Tags\Source', + 'throw' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', + 'throws' => '\phpDocumentor\Reflection\DocBlock\Tags\Throws', + 'uses' => '\phpDocumentor\Reflection\DocBlock\Tags\Uses', + 'var' => '\phpDocumentor\Reflection\DocBlock\Tags\Var_', + 'version' => '\phpDocumentor\Reflection\DocBlock\Tags\Version' + ]; + + /** + * @var \ReflectionParameter[][] a lazy-loading cache containing parameters for each tagHandler that has been used. + */ + private $tagHandlerParameterCache = []; + + /** + * @var FqsenResolver + */ + private $fqsenResolver; + + /** + * @var mixed[] an array representing a simple Service Locator where we can store parameters and + * services that can be inserted into the Factory Methods of Tag Handlers. + */ + private $serviceLocator = []; + + /** + * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers. + * + * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property + * is used. + * + * @param FqsenResolver $fqsenResolver + * @param string[] $tagHandlers + * + * @see self::registerTagHandler() to add a new tag handler to the existing default list. + */ + public function __construct(FqsenResolver $fqsenResolver, array $tagHandlers = null) + { + $this->fqsenResolver = $fqsenResolver; + if ($tagHandlers !== null) { + $this->tagHandlerMappings = $tagHandlers; + } + + $this->addService($fqsenResolver, FqsenResolver::class); + } + + /** + * {@inheritDoc} + */ + public function create($tagLine, TypeContext $context = null) + { + if (! $context) { + $context = new TypeContext(''); + } + + list($tagName, $tagBody) = $this->extractTagParts($tagLine); + + if ($tagBody !== '' && $tagBody[0] === '[') { + throw new \InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + return $this->createTag($tagBody, $tagName, $context); + } + + /** + * {@inheritDoc} + */ + public function addParameter($name, $value) + { + $this->serviceLocator[$name] = $value; + } + + /** + * {@inheritDoc} + */ + public function addService($service, $alias = null) + { + $this->serviceLocator[$alias ?: get_class($service)] = $service; + } + + /** + * {@inheritDoc} + */ + public function registerTagHandler($tagName, $handler) + { + Assert::stringNotEmpty($tagName); + Assert::stringNotEmpty($handler); + Assert::classExists($handler); + Assert::implementsInterface($handler, StaticMethod::class); + + if (strpos($tagName, '\\') && $tagName[0] !== '\\') { + throw new \InvalidArgumentException( + 'A namespaced tag must have a leading backslash as it must be fully qualified' + ); + } + + $this->tagHandlerMappings[$tagName] = $handler; + } + + /** + * Extracts all components for a tag. + * + * @param string $tagLine + * + * @return string[] + */ + private function extractTagParts($tagLine) + { + $matches = []; + if (! preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)/us', $tagLine, $matches)) { + throw new \InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + if (count($matches) < 3) { + $matches[] = ''; + } + + return array_slice($matches, 1); + } + + /** + * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the + * body was invalid. + * + * @param string $body + * @param string $name + * @param TypeContext $context + * + * @return Tag|null + */ + private function createTag($body, $name, TypeContext $context) + { + $handlerClassName = $this->findHandlerClassName($name, $context); + $arguments = $this->getArgumentsForParametersFromWiring( + $this->fetchParametersForHandlerFactoryMethod($handlerClassName), + $this->getServiceLocatorWithDynamicParameters($context, $name, $body) + ); + + return call_user_func_array([$handlerClassName, 'create'], $arguments); + } + + /** + * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). + * + * @param string $tagName + * @param TypeContext $context + * + * @return string + */ + private function findHandlerClassName($tagName, TypeContext $context) + { + $handlerClassName = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handlerClassName = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + // TODO: Annotation support is planned for a later stage and as such is disabled for now + // $tagName = (string)$this->fqsenResolver->resolve($tagName, $context); + // if (isset($this->annotationMappings[$tagName])) { + // $handlerClassName = $this->annotationMappings[$tagName]; + // } + } + + return $handlerClassName; + } + + /** + * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. + * + * @param \ReflectionParameter[] $parameters + * @param mixed[] $locator + * + * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters + * is provided with this method. + */ + private function getArgumentsForParametersFromWiring($parameters, $locator) + { + $arguments = []; + foreach ($parameters as $index => $parameter) { + $typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null; + if (isset($locator[$typeHint])) { + $arguments[] = $locator[$typeHint]; + continue; + } + + $parameterName = $parameter->getName(); + if (isset($locator[$parameterName])) { + $arguments[] = $locator[$parameterName]; + continue; + } + + $arguments[] = null; + } + + return $arguments; + } + + /** + * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given + * tag handler class name. + * + * @param string $handlerClassName + * + * @return \ReflectionParameter[] + */ + private function fetchParametersForHandlerFactoryMethod($handlerClassName) + { + if (! isset($this->tagHandlerParameterCache[$handlerClassName])) { + $methodReflection = new \ReflectionMethod($handlerClassName, 'create'); + $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); + } + + return $this->tagHandlerParameterCache[$handlerClassName]; + } + + /** + * Returns a copy of this class' Service Locator with added dynamic parameters, such as the tag's name, body and + * Context. + * + * @param TypeContext $context The Context (namespace and aliasses) that may be passed and is used to resolve FQSENs. + * @param string $tagName The name of the tag that may be passed onto the factory method of the Tag class. + * @param string $tagBody The body of the tag that may be passed onto the factory method of the Tag class. + * + * @return mixed[] + */ + private function getServiceLocatorWithDynamicParameters(TypeContext $context, $tagName, $tagBody) + { + $locator = array_merge( + $this->serviceLocator, + [ + 'name' => $tagName, + 'body' => $tagBody, + TypeContext::class => $context + ] + ); + + return $locator; + } + + /** + * Returns whether the given tag belongs to an annotation. + * + * @param string $tagContent + * + * @todo this method should be populated once we implement Annotation notation support. + * + * @return bool + */ + private function isAnnotation($tagContent) + { + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + + return false; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php new file mode 100644 index 0000000000..e765367883 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php @@ -0,0 +1,26 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\DocBlock\Tags\Formatter; + +interface Tag +{ + public function getName(); + + public static function create($body); + + public function render(Formatter $formatter = null); + + public function __toString(); +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php new file mode 100644 index 0000000000..3c1d1132bc --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php @@ -0,0 +1,93 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock; + +use phpDocumentor\Reflection\Types\Context as TypeContext; + +interface TagFactory +{ + /** + * Adds a parameter to the service locator that can be injected in a tag's factory method. + * + * When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to + * typehint a parameter in the signature so that we can use that interface or class name to inject a dependency + * (see {@see addService()} for more information on that). + * + * Another way is to check the name of the argument against the names in the Service Locator. With this method + * you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching + * name. + * + * Be aware that there are two reserved names: + * + * - name, representing the name of the tag. + * - body, representing the complete body of the tag. + * + * These parameters are injected at the last moment and will override any existing parameter with those names. + * + * @param string $name + * @param mixed $value + * + * @return void + */ + public function addParameter($name, $value); + + /** + * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided. + * + * When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter + * has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint. + * + * Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the + * interface is passed as alias then every time that interface is requested the provided service will be returned. + * + * @param object $service + * @param string $alias + * + * @return void + */ + public function addService($service); + + /** + * Factory method responsible for instantiating the correct sub type. + * + * @param string $tagLine The text for this tag, including description. + * @param TypeContext $context + * + * @throws \InvalidArgumentException if an invalid tag line was presented. + * + * @return Tag A new tag object. + */ + public function create($tagLine, TypeContext $context = null); + + /** + * Registers a handler for tags. + * + * If you want to use your own tags then you can use this method to instruct the TagFactory to register the name + * of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement the {@see Tag} interface (and thus + * the create method). + * + * @param string $tagName Name of tag to register a handler for. When registering a namespaced tag, the full + * name, along with a prefixing slash MUST be provided. + * @param string $handler FQCN of handler. + * + * @throws \InvalidArgumentException if the tag name is not a string + * @throws \InvalidArgumentException if the tag name is namespaced (contains backslashes) but does not start with + * a backslash + * @throws \InvalidArgumentException if the handler is not a string + * @throws \InvalidArgumentException if the handler is not an existing class + * @throws \InvalidArgumentException if the handler does not implement the {@see Tag} interface + * + * @return void + */ + public function registerTagHandler($tagName, $handler); +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php new file mode 100644 index 0000000000..29d7f1de17 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php @@ -0,0 +1,100 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use Webmozart\Assert\Assert; + +/** + * Reflection class for an {@}author tag in a Docblock. + */ +final class Author extends BaseTag implements Factory\StaticMethod +{ + /** @var string register that this is the author tag. */ + protected $name = 'author'; + + /** @var string The name of the author */ + private $authorName = ''; + + /** @var string The email of the author */ + private $authorEmail = ''; + + /** + * Initializes this tag with the author name and e-mail. + * + * @param string $authorName + * @param string $authorEmail + */ + public function __construct($authorName, $authorEmail) + { + Assert::string($authorName); + Assert::string($authorEmail); + if ($authorEmail && !filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException('The author tag does not have a valid e-mail address'); + } + + $this->authorName = $authorName; + $this->authorEmail = $authorEmail; + } + + /** + * Gets the author's name. + * + * @return string The author's name. + */ + public function getAuthorName() + { + return $this->authorName; + } + + /** + * Returns the author's email. + * + * @return string The author's email. + */ + public function getEmail() + { + return $this->authorEmail; + } + + /** + * Returns this tag in string form. + * + * @return string + */ + public function __toString() + { + return $this->authorName . (strlen($this->authorEmail) ? ' <' . $this->authorEmail . '>' : ''); + } + + /** + * Attempts to create a new Author object based on †he tag body. + * + * @param string $body + * + * @return static + */ + public static function create($body) + { + Assert::string($body); + + $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches); + if (!$splitTagContent) { + return null; + } + + $authorName = trim($matches[1]); + $email = isset($matches[2]) ? trim($matches[2]) : ''; + + return new static($authorName, $email); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php new file mode 100644 index 0000000000..14bb71771f --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php @@ -0,0 +1,52 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Description; + +/** + * Parses a tag definition for a DocBlock. + */ +abstract class BaseTag implements DocBlock\Tag +{ + /** @var string Name of the tag */ + protected $name = ''; + + /** @var Description|null Description of the tag. */ + protected $description; + + /** + * Gets the name of this tag. + * + * @return string The name of this tag. + */ + public function getName() + { + return $this->name; + } + + public function getDescription() + { + return $this->description; + } + + public function render(Formatter $formatter = null) + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php new file mode 100644 index 0000000000..8d65403fe4 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php @@ -0,0 +1,83 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\FqsenResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a @covers tag in a Docblock. + */ +final class Covers extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'covers'; + + /** @var Fqsen */ + private $refers = null; + + /** + * Initializes this tag. + * + * @param Fqsen $refers + * @param Description $description + */ + public function __construct(Fqsen $refers, Description $description = null) + { + $this->refers = $refers; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + DescriptionFactory $descriptionFactory = null, + FqsenResolver $resolver = null, + TypeContext $context = null + ) { + Assert::string($body); + Assert::notEmpty($body); + + $parts = preg_split('/\s+/Su', $body, 2); + + return new static( + $resolver->resolve($parts[0], $context), + $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context) + ); + } + + /** + * Returns the structural element this tag refers to. + * + * @return Fqsen + */ + public function getReference() + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + * + * @return string + */ + public function __toString() + { + return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php new file mode 100644 index 0000000000..822c305005 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php @@ -0,0 +1,97 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}deprecated tag in a Docblock. + */ +final class Deprecated extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'deprecated'; + + /** + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. + */ + const REGEX_VECTOR = '(?: + # Normal release vectors. + \d\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\s\:]+\:\s*\$[^\$]+\$ + )'; + + /** @var string The version vector. */ + private $version = ''; + + public function __construct($version = null, Description $description = null) + { + Assert::nullOrStringNotEmpty($version); + + $this->version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) + { + Assert::nullOrString($body); + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return new static( + null, + null !== $descriptionFactory ? $descriptionFactory->create($body, $context) : null + ); + } + + return new static( + $matches[1], + $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context) + ); + } + + /** + * Gets the version section of the tag. + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return $this->version . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php new file mode 100644 index 0000000000..ecb199b49d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php @@ -0,0 +1,176 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\Tag; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}example tag in a Docblock. + */ +final class Example extends BaseTag +{ + /** + * @var string Path to a file to use as an example. May also be an absolute URI. + */ + private $filePath; + + /** + * @var bool Whether the file path component represents an URI. This determines how the file portion + * appears at {@link getContent()}. + */ + private $isURI = false; + + /** + * @var int + */ + private $startingLine; + + /** + * @var int + */ + private $lineCount; + + public function __construct($filePath, $isURI, $startingLine, $lineCount, $description) + { + Assert::notEmpty($filePath); + Assert::integer($startingLine); + Assert::greaterThanEq($startingLine, 0); + + $this->filePath = $filePath; + $this->startingLine = $startingLine; + $this->lineCount = $lineCount; + $this->name = 'example'; + if ($description !== null) { + $this->description = trim($description); + } + + $this->isURI = $isURI; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->description) { + $filePath = '"' . $this->filePath . '"'; + if ($this->isURI) { + $filePath = $this->isUriRelative($this->filePath) + ? str_replace('%2F', '/', rawurlencode($this->filePath)) + :$this->filePath; + } + + return trim($filePath . ' ' . parent::getDescription()); + } + + return $this->description; + } + + /** + * {@inheritdoc} + */ + public static function create($body) + { + // File component: File path in quotes or File URI / Source information + if (! preg_match('/^(?:\"([^\"]+)\"|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) { + return null; + } + + $filePath = null; + $fileUri = null; + if ('' !== $matches[1]) { + $filePath = $matches[1]; + } else { + $fileUri = $matches[2]; + } + + $startingLine = 1; + $lineCount = null; + $description = null; + + if (array_key_exists(3, $matches)) { + $description = $matches[3]; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) { + $startingLine = (int)$contentMatches[1]; + if (isset($contentMatches[2]) && $contentMatches[2] !== '') { + $lineCount = (int)$contentMatches[2]; + } + + if (array_key_exists(3, $contentMatches)) { + $description = $contentMatches[3]; + } + } + } + + return new static( + $filePath !== null?$filePath:$fileUri, + $fileUri !== null, + $startingLine, + $lineCount, + $description + ); + } + + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath() + { + return $this->filePath; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return $this->filePath . ($this->description ? ' ' . $this->description : ''); + } + + /** + * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). + * + * @param string $uri + * + * @return bool + */ + private function isUriRelative($uri) + { + return false === strpos($uri, ':'); + } + + /** + * @return int + */ + public function getStartingLine() + { + return $this->startingLine; + } + + /** + * @return int + */ + public function getLineCount() + { + return $this->lineCount; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php new file mode 100644 index 0000000000..98aea455c1 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php @@ -0,0 +1,18 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; + +interface StaticMethod +{ + public static function create($body); +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php new file mode 100644 index 0000000000..b9ca0b8abe --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php @@ -0,0 +1,18 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Factory; + +interface Strategy +{ + public function create($body); +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php new file mode 100644 index 0000000000..64b2c60349 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php @@ -0,0 +1,27 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Tag; + +interface Formatter +{ + /** + * Formats a tag into a string representation according to a specific format, such as Markdown. + * + * @param Tag $tag + * + * @return string + */ + public function format(Tag $tag); +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php new file mode 100644 index 0000000000..ceb40cc321 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php @@ -0,0 +1,47 @@ + + * @copyright 2017 Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter; + +use phpDocumentor\Reflection\DocBlock\Tag; +use phpDocumentor\Reflection\DocBlock\Tags\Formatter; + +class AlignFormatter implements Formatter +{ + /** @var int The maximum tag name length. */ + protected $maxLen = 0; + + /** + * Constructor. + * + * @param Tag[] $tags All tags that should later be aligned with the formatter. + */ + public function __construct(array $tags) + { + foreach ($tags as $tag) { + $this->maxLen = max($this->maxLen, strlen($tag->getName())); + } + } + + /** + * Formats the given tag to return a simple plain text version. + * + * @param Tag $tag + * + * @return string + */ + public function format(Tag $tag) + { + return '@' . $tag->getName() . str_repeat(' ', $this->maxLen - strlen($tag->getName()) + 1) . (string)$tag; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php new file mode 100644 index 0000000000..4e2c576268 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter; + +use phpDocumentor\Reflection\DocBlock\Tag; +use phpDocumentor\Reflection\DocBlock\Tags\Formatter; + +class PassthroughFormatter implements Formatter +{ + /** + * Formats the given tag to return a simple plain text version. + * + * @param Tag $tag + * + * @return string + */ + public function format(Tag $tag) + { + return trim('@' . $tag->getName() . ' ' . (string)$tag); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php new file mode 100644 index 0000000000..e4c53e00a6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php @@ -0,0 +1,91 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\DocBlock\StandardTagFactory; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Parses a tag definition for a DocBlock. + */ +class Generic extends BaseTag implements Factory\StaticMethod +{ + /** + * Parses a tag and populates the member variables. + * + * @param string $name Name of the tag. + * @param Description $description The contents of the given tag. + */ + public function __construct($name, Description $description = null) + { + $this->validateTagName($name); + + $this->name = $name; + $this->description = $description; + } + + /** + * Creates a new tag that represents any unknown tag type. + * + * @param string $body + * @param string $name + * @param DescriptionFactory $descriptionFactory + * @param TypeContext $context + * + * @return static + */ + public static function create( + $body, + $name = '', + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::string($body); + Assert::stringNotEmpty($name); + Assert::notNull($descriptionFactory); + + $description = $descriptionFactory && $body ? $descriptionFactory->create($body, $context) : null; + + return new static($name, $description); + } + + /** + * Returns the tag as a serialized string + * + * @return string + */ + public function __toString() + { + return ($this->description ? $this->description->render() : ''); + } + + /** + * Validates if the tag name matches the expected format, otherwise throws an exception. + * + * @param string $name + * + * @return void + */ + private function validateTagName($name) + { + if (! preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { + throw new \InvalidArgumentException( + 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' + . 'hyphens and backslashes.' + ); + } + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php new file mode 100644 index 0000000000..9c0e367eb0 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php @@ -0,0 +1,77 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a @link tag in a Docblock. + */ +final class Link extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'link'; + + /** @var string */ + private $link = ''; + + /** + * Initializes a link to a URL. + * + * @param string $link + * @param Description $description + */ + public function __construct($link, Description $description = null) + { + Assert::string($link); + + $this->link = $link; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) + { + Assert::string($body); + Assert::notNull($descriptionFactory); + + $parts = preg_split('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + return new static($parts[0], $description); + } + + /** + * Gets the link + * + * @return string + */ + public function getLink() + { + return $this->link; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return $this->link . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php new file mode 100644 index 0000000000..7522529923 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php @@ -0,0 +1,242 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use phpDocumentor\Reflection\Types\Void_; +use Webmozart\Assert\Assert; + +/** + * Reflection class for an {@}method in a Docblock. + */ +final class Method extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'method'; + + /** @var string */ + private $methodName = ''; + + /** @var string[] */ + private $arguments = []; + + /** @var bool */ + private $isStatic = false; + + /** @var Type */ + private $returnType; + + public function __construct( + $methodName, + array $arguments = [], + Type $returnType = null, + $static = false, + Description $description = null + ) { + Assert::stringNotEmpty($methodName); + Assert::boolean($static); + + if ($returnType === null) { + $returnType = new Void_(); + } + + $this->methodName = $methodName; + $this->arguments = $this->filterArguments($arguments); + $this->returnType = $returnType; + $this->isStatic = $static; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::stringNotEmpty($body); + Assert::allNotNull([ $typeResolver, $descriptionFactory ]); + + // 1. none or more whitespace + // 2. optionally the keyword "static" followed by whitespace + // 3. optionally a word with underscores followed by whitespace : as + // type for the return value + // 4. then optionally a word with underscores followed by () and + // whitespace : as method name as used by phpDocumentor + // 5. then a word with underscores, followed by ( and any character + // until a ) and whitespace : as method name with signature + // 6. any remaining text : as description + if (!preg_match( + '/^ + # Static keyword + # Declares a static method ONLY if type is also present + (?: + (static) + \s+ + )? + # Return type + (?: + ( + (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*) + | + (?: + (?:[\w\|_\\\\]+) + # array notation + (?:\[\])* + )* + ) + \s+ + )? + # Legacy method name (not captured) + (?: + [\w_]+\(\)\s+ + )? + # Method name + ([\w\|_\\\\]+) + # Arguments + (?: + \(([^\)]*)\) + )? + \s* + # Description + (.*) + $/sux', + $body, + $matches + )) { + return null; + } + + list(, $static, $returnType, $methodName, $arguments, $description) = $matches; + + $static = $static === 'static'; + + if ($returnType === '') { + $returnType = 'void'; + } + + $returnType = $typeResolver->resolve($returnType, $context); + $description = $descriptionFactory->create($description, $context); + + if (is_string($arguments) && strlen($arguments) > 0) { + $arguments = explode(',', $arguments); + foreach ($arguments as &$argument) { + $argument = explode(' ', self::stripRestArg(trim($argument)), 2); + if ($argument[0][0] === '$') { + $argumentName = substr($argument[0], 1); + $argumentType = new Void_(); + } else { + $argumentType = $typeResolver->resolve($argument[0], $context); + $argumentName = ''; + if (isset($argument[1])) { + $argument[1] = self::stripRestArg($argument[1]); + $argumentName = substr($argument[1], 1); + } + } + + $argument = [ 'name' => $argumentName, 'type' => $argumentType]; + } + } else { + $arguments = []; + } + + return new static($methodName, $arguments, $returnType, $static, $description); + } + + /** + * Retrieves the method name. + * + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * @return string[] + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. + */ + public function isStatic() + { + return $this->isStatic; + } + + /** + * @return Type + */ + public function getReturnType() + { + return $this->returnType; + } + + public function __toString() + { + $arguments = []; + foreach ($this->arguments as $argument) { + $arguments[] = $argument['type'] . ' $' . $argument['name']; + } + + return trim(($this->isStatic() ? 'static ' : '') + . (string)$this->returnType . ' ' + . $this->methodName + . '(' . implode(', ', $arguments) . ')' + . ($this->description ? ' ' . $this->description->render() : '')); + } + + private function filterArguments($arguments) + { + foreach ($arguments as &$argument) { + if (is_string($argument)) { + $argument = [ 'name' => $argument ]; + } + + if (! isset($argument['type'])) { + $argument['type'] = new Void_(); + } + + $keys = array_keys($argument); + sort($keys); + if ($keys !== [ 'name', 'type' ]) { + throw new \InvalidArgumentException( + 'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true) + ); + } + } + + return $arguments; + } + + private static function stripRestArg($argument) + { + if (strpos($argument, '...') === 0) { + $argument = trim(substr($argument, 3)); + } + + return $argument; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php new file mode 100644 index 0000000000..7d699d88d4 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php @@ -0,0 +1,141 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for the {@}param tag in a Docblock. + */ +final class Param extends BaseTag implements Factory\StaticMethod +{ + /** @var string */ + protected $name = 'param'; + + /** @var Type */ + private $type; + + /** @var string */ + private $variableName = ''; + + /** @var bool determines whether this is a variadic argument */ + private $isVariadic = false; + + /** + * @param string $variableName + * @param Type $type + * @param bool $isVariadic + * @param Description $description + */ + public function __construct($variableName, Type $type = null, $isVariadic = false, Description $description = null) + { + Assert::string($variableName); + Assert::boolean($isVariadic); + + $this->variableName = $variableName; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::stringNotEmpty($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + $isVariadic = false; + + // if the first item that is encountered is not a variable; it is a type + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { + $type = $typeResolver->resolve(array_shift($parts), $context); + array_shift($parts); + } + + // if the next item starts with a $ or ...$ it must be the variable name + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$' || substr($parts[0], 0, 4) === '...$')) { + $variableName = array_shift($parts); + array_shift($parts); + + if (substr($variableName, 0, 3) === '...') { + $isVariadic = true; + $variableName = substr($variableName, 3); + } + + if (substr($variableName, 0, 1) === '$') { + $variableName = substr($variableName, 1); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $isVariadic, $description); + } + + /** + * Returns the variable's name. + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Returns the variable's type or null if unknown. + * + * @return Type|null + */ + public function getType() + { + return $this->type; + } + + /** + * Returns whether this tag is variadic. + * + * @return boolean + */ + public function isVariadic() + { + return $this->isVariadic; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return ($this->type ? $this->type . ' ' : '') + . ($this->isVariadic() ? '...' : '') + . '$' . $this->variableName + . ($this->description ? ' ' . $this->description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php new file mode 100644 index 0000000000..f0ef7c07b3 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php @@ -0,0 +1,118 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}property tag in a Docblock. + */ +class Property extends BaseTag implements Factory\StaticMethod +{ + /** @var string */ + protected $name = 'property'; + + /** @var Type */ + private $type; + + /** @var string */ + protected $variableName = ''; + + /** + * @param string $variableName + * @param Type $type + * @param Description $description + */ + public function __construct($variableName, Type $type = null, Description $description = null) + { + Assert::string($variableName); + + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::stringNotEmpty($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { + $type = $typeResolver->resolve(array_shift($parts), $context); + array_shift($parts); + } + + // if the next item starts with a $ or ...$ it must be the variable name + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) { + $variableName = array_shift($parts); + array_shift($parts); + + if (substr($variableName, 0, 1) === '$') { + $variableName = substr($variableName, 1); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Returns the variable's type or null if unknown. + * + * @return Type|null + */ + public function getType() + { + return $this->type; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return ($this->type ? $this->type . ' ' : '') + . '$' . $this->variableName + . ($this->description ? ' ' . $this->description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php new file mode 100644 index 0000000000..e41c0c1cef --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php @@ -0,0 +1,118 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}property-read tag in a Docblock. + */ +class PropertyRead extends BaseTag implements Factory\StaticMethod +{ + /** @var string */ + protected $name = 'property-read'; + + /** @var Type */ + private $type; + + /** @var string */ + protected $variableName = ''; + + /** + * @param string $variableName + * @param Type $type + * @param Description $description + */ + public function __construct($variableName, Type $type = null, Description $description = null) + { + Assert::string($variableName); + + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::stringNotEmpty($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { + $type = $typeResolver->resolve(array_shift($parts), $context); + array_shift($parts); + } + + // if the next item starts with a $ or ...$ it must be the variable name + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) { + $variableName = array_shift($parts); + array_shift($parts); + + if (substr($variableName, 0, 1) === '$') { + $variableName = substr($variableName, 1); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Returns the variable's type or null if unknown. + * + * @return Type|null + */ + public function getType() + { + return $this->type; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return ($this->type ? $this->type . ' ' : '') + . '$' . $this->variableName + . ($this->description ? ' ' . $this->description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php new file mode 100644 index 0000000000..cfdb0ed000 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php @@ -0,0 +1,118 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}property-write tag in a Docblock. + */ +class PropertyWrite extends BaseTag implements Factory\StaticMethod +{ + /** @var string */ + protected $name = 'property-write'; + + /** @var Type */ + private $type; + + /** @var string */ + protected $variableName = ''; + + /** + * @param string $variableName + * @param Type $type + * @param Description $description + */ + public function __construct($variableName, Type $type = null, Description $description = null) + { + Assert::string($variableName); + + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::stringNotEmpty($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { + $type = $typeResolver->resolve(array_shift($parts), $context); + array_shift($parts); + } + + // if the next item starts with a $ or ...$ it must be the variable name + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) { + $variableName = array_shift($parts); + array_shift($parts); + + if (substr($variableName, 0, 1) === '$') { + $variableName = substr($variableName, 1); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Returns the variable's type or null if unknown. + * + * @return Type|null + */ + public function getType() + { + return $this->type; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return ($this->type ? $this->type . ' ' : '') + . '$' . $this->variableName + . ($this->description ? ' ' . $this->description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php new file mode 100644 index 0000000000..dc7b8b6d42 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php @@ -0,0 +1,42 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Reference; + +use phpDocumentor\Reflection\Fqsen as RealFqsen; + +/** + * Fqsen reference used by {@see phpDocumentor\Reflection\DocBlock\Tags\See} + */ +final class Fqsen implements Reference +{ + /** + * @var RealFqsen + */ + private $fqsen; + + /** + * Fqsen constructor. + */ + public function __construct(RealFqsen $fqsen) + { + $this->fqsen = $fqsen; + } + + /** + * @return string string representation of the referenced fqsen + */ + public function __toString() + { + return (string)$this->fqsen; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php new file mode 100644 index 0000000000..a3ffd24c90 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php @@ -0,0 +1,21 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Reference; + +/** + * Interface for references in {@see phpDocumentor\Reflection\DocBlock\Tags\See} + */ +interface Reference +{ + public function __toString(); +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php new file mode 100644 index 0000000000..2671d5e13c --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php @@ -0,0 +1,40 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags\Reference; + +use Webmozart\Assert\Assert; + +/** + * Url reference used by {@see phpDocumentor\Reflection\DocBlock\Tags\See} + */ +final class Url implements Reference +{ + /** + * @var string + */ + private $uri; + + /** + * Url constructor. + */ + public function __construct($uri) + { + Assert::stringNotEmpty($uri); + $this->uri = $uri; + } + + public function __toString() + { + return $this->uri; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php new file mode 100644 index 0000000000..ca5bda705b --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php @@ -0,0 +1,72 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}return tag in a Docblock. + */ +final class Return_ extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'return'; + + /** @var Type */ + private $type; + + public function __construct(Type $type, Description $description = null) + { + $this->type = $type; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::string($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/\s+/Su', $body, 2); + + $type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context); + $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context); + + return new static($type, $description); + } + + /** + * Returns the type section of the variable. + * + * @return Type + */ + public function getType() + { + return $this->type; + } + + public function __toString() + { + return $this->type . ' ' . $this->description; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php new file mode 100644 index 0000000000..9e9e723bd8 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php @@ -0,0 +1,88 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Reference; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url; +use phpDocumentor\Reflection\FqsenResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for an {@}see tag in a Docblock. + */ +class See extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'see'; + + /** @var Reference */ + protected $refers = null; + + /** + * Initializes this tag. + * + * @param Reference $refers + * @param Description $description + */ + public function __construct(Reference $refers, Description $description = null) + { + $this->refers = $refers; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + FqsenResolver $resolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::string($body); + Assert::allNotNull([$resolver, $descriptionFactory]); + + $parts = preg_split('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + // https://tools.ietf.org/html/rfc2396#section-3 + if (preg_match('/\w:\/\/\w/i', $parts[0])) { + return new static(new Url($parts[0]), $description); + } + + return new static(new FqsenRef($resolver->resolve($parts[0], $context)), $description); + } + + /** + * Returns the ref of this tag. + * + * @return Reference + */ + public function getReference() + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + * + * @return string + */ + public function __toString() + { + return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php new file mode 100644 index 0000000000..835fb0dcdc --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php @@ -0,0 +1,94 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}since tag in a Docblock. + */ +final class Since extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'since'; + + /** + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. + */ + const REGEX_VECTOR = '(?: + # Normal release vectors. + \d\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\s\:]+\:\s*\$[^\$]+\$ + )'; + + /** @var string The version vector. */ + private $version = ''; + + public function __construct($version = null, Description $description = null) + { + Assert::nullOrStringNotEmpty($version); + + $this->version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) + { + Assert::nullOrString($body); + if (empty($body)) { + return new static(); + } + + $matches = []; + if (! preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + return new static( + $matches[1], + $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context) + ); + } + + /** + * Gets the version section of the tag. + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return $this->version . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php new file mode 100644 index 0000000000..247b1b3bab --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php @@ -0,0 +1,97 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}source tag in a Docblock. + */ +final class Source extends BaseTag implements Factory\StaticMethod +{ + /** @var string */ + protected $name = 'source'; + + /** @var int The starting line, relative to the structural element's location. */ + private $startingLine = 1; + + /** @var int|null The number of lines, relative to the starting line. NULL means "to the end". */ + private $lineCount = null; + + public function __construct($startingLine, $lineCount = null, Description $description = null) + { + Assert::integerish($startingLine); + Assert::nullOrIntegerish($lineCount); + + $this->startingLine = (int)$startingLine; + $this->lineCount = $lineCount !== null ? (int)$lineCount : null; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) + { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + + $startingLine = 1; + $lineCount = null; + $description = null; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) { + $startingLine = (int)$matches[1]; + if (isset($matches[2]) && $matches[2] !== '') { + $lineCount = (int)$matches[2]; + } + + $description = $matches[3]; + } + + return new static($startingLine, $lineCount, $descriptionFactory->create($description, $context)); + } + + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine() + { + return $this->startingLine; + } + + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount() + { + return $this->lineCount; + } + + public function __toString() + { + return $this->startingLine + . ($this->lineCount !== null ? ' ' . $this->lineCount : '') + . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php new file mode 100644 index 0000000000..349e773bbe --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php @@ -0,0 +1,72 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}throws tag in a Docblock. + */ +final class Throws extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'throws'; + + /** @var Type */ + private $type; + + public function __construct(Type $type, Description $description = null) + { + $this->type = $type; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::string($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/\s+/Su', $body, 2); + + $type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context); + $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context); + + return new static($type, $description); + } + + /** + * Returns the type section of the variable. + * + * @return Type + */ + public function getType() + { + return $this->type; + } + + public function __toString() + { + return $this->type . ' ' . $this->description; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php new file mode 100644 index 0000000000..00dc3e3bc8 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php @@ -0,0 +1,83 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\FqsenResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}uses tag in a Docblock. + */ +final class Uses extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'uses'; + + /** @var Fqsen */ + protected $refers = null; + + /** + * Initializes this tag. + * + * @param Fqsen $refers + * @param Description $description + */ + public function __construct(Fqsen $refers, Description $description = null) + { + $this->refers = $refers; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + FqsenResolver $resolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::string($body); + Assert::allNotNull([$resolver, $descriptionFactory]); + + $parts = preg_split('/\s+/Su', $body, 2); + + return new static( + $resolver->resolve($parts[0], $context), + $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context) + ); + } + + /** + * Returns the structural element this tag refers to. + * + * @return Fqsen + */ + public function getReference() + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + * + * @return string + */ + public function __toString() + { + return $this->refers . ' ' . $this->description->render(); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php new file mode 100644 index 0000000000..8907c951f0 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php @@ -0,0 +1,118 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Type; +use phpDocumentor\Reflection\TypeResolver; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}var tag in a Docblock. + */ +class Var_ extends BaseTag implements Factory\StaticMethod +{ + /** @var string */ + protected $name = 'var'; + + /** @var Type */ + private $type; + + /** @var string */ + protected $variableName = ''; + + /** + * @param string $variableName + * @param Type $type + * @param Description $description + */ + public function __construct($variableName, Type $type = null, Description $description = null) + { + Assert::string($variableName); + + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + /** + * {@inheritdoc} + */ + public static function create( + $body, + TypeResolver $typeResolver = null, + DescriptionFactory $descriptionFactory = null, + TypeContext $context = null + ) { + Assert::stringNotEmpty($body); + Assert::allNotNull([$typeResolver, $descriptionFactory]); + + $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) { + $type = $typeResolver->resolve(array_shift($parts), $context); + array_shift($parts); + } + + // if the next item starts with a $ or ...$ it must be the variable name + if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) { + $variableName = array_shift($parts); + array_shift($parts); + + if (substr($variableName, 0, 1) === '$') { + $variableName = substr($variableName, 1); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Returns the variable's type or null if unknown. + * + * @return Type|null + */ + public function getType() + { + return $this->type; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return ($this->type ? $this->type . ' ' : '') + . (empty($this->variableName) ? null : ('$' . $this->variableName)) + . ($this->description ? ' ' . $this->description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php new file mode 100644 index 0000000000..7bb0420730 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php @@ -0,0 +1,94 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\DocBlock\Tags; + +use phpDocumentor\Reflection\DocBlock\Description; +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\Types\Context as TypeContext; +use Webmozart\Assert\Assert; + +/** + * Reflection class for a {@}version tag in a Docblock. + */ +final class Version extends BaseTag implements Factory\StaticMethod +{ + protected $name = 'version'; + + /** + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. + */ + const REGEX_VECTOR = '(?: + # Normal release vectors. + \d\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\s\:]+\:\s*\$[^\$]+\$ + )'; + + /** @var string The version vector. */ + private $version = ''; + + public function __construct($version = null, Description $description = null) + { + Assert::nullOrStringNotEmpty($version); + + $this->version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null) + { + Assert::nullOrString($body); + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + return new static( + $matches[1], + $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context) + ); + } + + /** + * Gets the version section of the tag. + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + * + * @return string + */ + public function __toString() + { + return $this->version . ($this->description ? ' ' . $this->description->render() : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php new file mode 100644 index 0000000000..1bdb8f4dfa --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php @@ -0,0 +1,277 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +use phpDocumentor\Reflection\DocBlock\DescriptionFactory; +use phpDocumentor\Reflection\DocBlock\StandardTagFactory; +use phpDocumentor\Reflection\DocBlock\Tag; +use phpDocumentor\Reflection\DocBlock\TagFactory; +use Webmozart\Assert\Assert; + +final class DocBlockFactory implements DocBlockFactoryInterface +{ + /** @var DocBlock\DescriptionFactory */ + private $descriptionFactory; + + /** @var DocBlock\TagFactory */ + private $tagFactory; + + /** + * Initializes this factory with the required subcontractors. + * + * @param DescriptionFactory $descriptionFactory + * @param TagFactory $tagFactory + */ + public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory) + { + $this->descriptionFactory = $descriptionFactory; + $this->tagFactory = $tagFactory; + } + + /** + * Factory method for easy instantiation. + * + * @param string[] $additionalTags + * + * @return DocBlockFactory + */ + public static function createInstance(array $additionalTags = []) + { + $fqsenResolver = new FqsenResolver(); + $tagFactory = new StandardTagFactory($fqsenResolver); + $descriptionFactory = new DescriptionFactory($tagFactory); + + $tagFactory->addService($descriptionFactory); + $tagFactory->addService(new TypeResolver($fqsenResolver)); + + $docBlockFactory = new self($descriptionFactory, $tagFactory); + foreach ($additionalTags as $tagName => $tagHandler) { + $docBlockFactory->registerTagHandler($tagName, $tagHandler); + } + + return $docBlockFactory; + } + + /** + * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the + * getDocComment method (such as a ReflectionClass object). + * @param Types\Context $context + * @param Location $location + * + * @return DocBlock + */ + public function create($docblock, Types\Context $context = null, Location $location = null) + { + if (is_object($docblock)) { + if (!method_exists($docblock, 'getDocComment')) { + $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method'; + throw new \InvalidArgumentException($exceptionMessage); + } + + $docblock = $docblock->getDocComment(); + } + + Assert::stringNotEmpty($docblock); + + if ($context === null) { + $context = new Types\Context(''); + } + + $parts = $this->splitDocBlock($this->stripDocComment($docblock)); + list($templateMarker, $summary, $description, $tags) = $parts; + + return new DocBlock( + $summary, + $description ? $this->descriptionFactory->create($description, $context) : null, + array_filter($this->parseTagBlock($tags, $context), function ($tag) { + return $tag instanceof Tag; + }), + $context, + $location, + $templateMarker === '#@+', + $templateMarker === '#@-' + ); + } + + public function registerTagHandler($tagName, $handler) + { + $this->tagFactory->registerTagHandler($tagName, $handler); + } + + /** + * Strips the asterisks from the DocBlock comment. + * + * @param string $comment String containing the comment text. + * + * @return string + */ + private function stripDocComment($comment) + { + $comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment)); + + // reg ex above is not able to remove */ from a single line docblock + if (substr($comment, -2) === '*/') { + $comment = trim(substr($comment, 0, -2)); + } + + return str_replace(["\r\n", "\r"], "\n", $comment); + } + + /** + * Splits the DocBlock into a template marker, summary, description and block of tags. + * + * @param string $comment Comment to split into the sub-parts. + * + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + * @author Mike van Riel for extending the regex with template marker support. + * + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. + */ + private function splitDocBlock($comment) + { + // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This + // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the + // performance impact of running a regular expression + if (strpos($comment, '@') === 0) { + return ['', '', '', $comment]; + } + + // clears all extra horizontal whitespace from the line endings to prevent parsing issues + $comment = preg_replace('/\h*$/Sum', '', $comment); + + /* + * Splits the docblock into a template marker, summary, description and tags section. + * + * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may + * occur after it and will be stripped). + * - The short description is started from the first character until a dot is encountered followed by a + * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing + * errors). This is optional. + * - The long description, any character until a new line is encountered followed by an @ and word + * characters (a tag). This is optional. + * - Tags; the remaining characters + * + * Big thanks to RichardJ for contributing this Regular Expression + */ + preg_match( + '/ + \A + # 1. Extract the template marker + (?:(\#\@\+|\#\@\-)\n?)? + + # 2. Extract the summary + (?: + (?! @\pL ) # The summary may not start with an @ + ( + [^\n.]+ + (?: + (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines + [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line + [^\n.]+ # Include anything else + )* + \.? + )? + ) + + # 3. Extract the description + (?: + \s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\pL ) # The description may not start with an @ + ( + [^\n]+ + (?: \n+ + (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line + [^\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\s+ [\s\S]*)? # everything that follows + /ux', + $comment, + $matches + ); + array_shift($matches); + + while (count($matches) < 4) { + $matches[] = ''; + } + + return $matches; + } + + /** + * Creates the tag objects. + * + * @param string $tags Tag block to parse. + * @param Types\Context $context Context of the parsed Tag + * + * @return DocBlock\Tag[] + */ + private function parseTagBlock($tags, Types\Context $context) + { + $tags = $this->filterTagBlock($tags); + if (!$tags) { + return []; + } + + $result = $this->splitTagBlockIntoTagLines($tags); + foreach ($result as $key => $tagLine) { + $result[$key] = $this->tagFactory->create(trim($tagLine), $context); + } + + return $result; + } + + /** + * @param string $tags + * + * @return string[] + */ + private function splitTagBlockIntoTagLines($tags) + { + $result = []; + foreach (explode("\n", $tags) as $tag_line) { + if (isset($tag_line[0]) && ($tag_line[0] === '@')) { + $result[] = $tag_line; + } else { + $result[count($result) - 1] .= "\n" . $tag_line; + } + } + + return $result; + } + + /** + * @param $tags + * @return string + */ + private function filterTagBlock($tags) + { + $tags = trim($tags); + if (!$tags) { + return null; + } + + if ('@' !== $tags[0]) { + // @codeCoverageIgnoreStart + // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that + // we didn't foresee. + throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags); + // @codeCoverageIgnoreEnd + } + + return $tags; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php new file mode 100644 index 0000000000..b35334295f --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php @@ -0,0 +1,23 @@ + please note that if you want to pass partial class names that additional steps are necessary, see the + > chapter `Resolving partial classes and FQSENs` for more information. + +Where the FqsenResolver can resolve: + +- Constant expressions (i.e. `@see \MyNamespace\MY_CONSTANT`) +- Function expressions (i.e. `@see \MyNamespace\myFunction()`) +- Class expressions (i.e. `@see \MyNamespace\MyClass`) +- Interface expressions (i.e. `@see \MyNamespace\MyInterface`) +- Trait expressions (i.e. `@see \MyNamespace\MyTrait`) +- Class constant expressions (i.e. `@see \MyNamespace\MyClass::MY_CONSTANT`) +- Property expressions (i.e. `@see \MyNamespace\MyClass::$myProperty`) +- Method expressions (i.e. `@see \MyNamespace\MyClass::myMethod()`) + +## Resolving a type + +In order to resolve a type you will have to instantiate the class `\phpDocumentor\Reflection\TypeResolver` +and call its `resolve` method like this: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('string|integer'); +``` + +In this example you will receive a Value Object of class `\phpDocumentor\Reflection\Types\Compound` that has two +elements, one of type `\phpDocumentor\Reflection\Types\String_` and one of type +`\phpDocumentor\Reflection\Types\Integer`. + +The real power of this resolver is in its capability to expand partial class names into fully qualified class names; but +in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver +in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +## Resolving an FQSEN + +A Fully Qualified Structural Element Name is a reference to another element in your code bases and can be resolved using +the `\phpDocumentor\Reflection\FqsenResolver` class' `resolve` method, like this: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$fqsen = $fqsenResolver->resolve('\phpDocumentor\Reflection\FqsenResolver::resolve()'); +``` + +In this example we resolve a Fully Qualified Structural Element Name (meaning that it includes the full namespace, class +name and element name) and receive a Value Object of type `\phpDocumentor\Reflection\Fqsen`. + +The real power of this resolver is in its capability to expand partial element names into Fully Qualified Structural +Element Names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will +inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply. + +## Resolving partial Classes and Structural Element Names + +Perhaps the best feature of this library is that it knows how to resolve partial class names into fully qualified class +names. + +For example, you have this file: + +```php +namespace My\Example; + +use phpDocumentor\Reflection\Types; + +class Classy +{ + /** + * @var Types\Context + * @see Classy::otherFunction() + */ + public function __construct($context) {} + + public function otherFunction(){} +} +``` + +Suppose that you would want to resolve (and expand) the type in the `@var` tag and the element name in the `@see` tag. +For the resolvers to know how to expand partial names you have to provide a bit of _Context_ for them by instantiating +a new class named `\phpDocumentor\Reflection\Types\Context` with the name of the namespace and the aliases that are in +play. + +### Creating a Context + +You can do this by manually creating a Context like this: + +```php +$context = new \phpDocumentor\Reflection\Types\Context( + '\My\Example', + [ 'Types' => '\phpDocumentor\Reflection\Types'] +); +``` + +Or by using the `\phpDocumentor\Reflection\Types\ContextFactory` to instantiate a new context based on a Reflector +object or by providing the namespace that you'd like to extract and the source code of the file in which the given +type expression occurs. + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createFromReflector(new ReflectionMethod('\My\Example\Classy', '__construct')); +``` + +or + +```php +$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory(); +$context = $contextFactory->createForNamespace('\My\Example', file_get_contents('My/Example/Classy.php')); +``` + +### Using the Context + +After you have obtained a Context it is just a matter of passing it along with the `resolve` method of either Resolver +class as second argument and the Resolvers will take this into account when resolving partial names. + +To obtain the resolved class name for the `@var` tag in the example above you can do: + +```php +$typeResolver = new \phpDocumentor\Reflection\TypeResolver(); +$type = $typeResolver->resolve('Types\Context', $context); +``` + +When you do this you will receive an object of class `\phpDocumentor\Reflection\Types\Object_` for which you can call +the `getFqsen` method to receive a Value Object that represents the complete FQSEN. So that would be +`phpDocumentor\Reflection\Types\Context`. + +> Why is the FQSEN wrapped in another object `Object_`? +> +> The resolve method of the TypeResolver only returns object with the interface `Type` and the FQSEN is a common +> type that does not represent a Type. Also: in some cases a type can represent an "Untyped Object", meaning that it +> is an object (signified by the `object` keyword) but does not refer to a specific element using an FQSEN. + +Another example is on how to resolve the FQSEN of a method as can be seen with the `@see` tag in the example above. To +resolve that you can do the following: + +```php +$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver(); +$type = $fqsenResolver->resolve('Classy::otherFunction()', $context); +``` + +Because Classy is a Class in the current namespace its FQSEN will have the `My\Example` namespace and by calling the +`resolve` method of the FQSEN Resolver you will receive an `Fqsen` object that refers to +`\My\Example\Classy::otherFunction()`. diff --git a/vendor/phpdocumentor/type-resolver/composer.json b/vendor/phpdocumentor/type-resolver/composer.json new file mode 100644 index 0000000000..82ead1564c --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/composer.json @@ -0,0 +1,27 @@ +{ + "name": "phpdocumentor/type-resolver", + "type": "library", + "license": "MIT", + "authors": [ + {"name": "Mike van Riel", "email": "me@mikevanriel.com"} + ], + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "autoload": { + "psr-4": {"phpDocumentor\\Reflection\\": ["src/"]} + }, + "autoload-dev": { + "psr-4": {"phpDocumentor\\Reflection\\": ["tests/unit"]} + }, + "require-dev": { + "phpunit/phpunit": "^5.2||^4.8.24", + "mockery/mockery": "^0.9.4" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/FqsenResolver.php b/vendor/phpdocumentor/type-resolver/src/FqsenResolver.php new file mode 100644 index 0000000000..9aa6ba3050 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/FqsenResolver.php @@ -0,0 +1,77 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +use phpDocumentor\Reflection\Types\Context; + +class FqsenResolver +{ + /** @var string Definition of the NAMESPACE operator in PHP */ + const OPERATOR_NAMESPACE = '\\'; + + public function resolve($fqsen, Context $context = null) + { + if ($context === null) { + $context = new Context(''); + } + + if ($this->isFqsen($fqsen)) { + return new Fqsen($fqsen); + } + + return $this->resolvePartialStructuralElementName($fqsen, $context); + } + + /** + * Tests whether the given type is a Fully Qualified Structural Element Name. + * + * @param string $type + * + * @return bool + */ + private function isFqsen($type) + { + return strpos($type, self::OPERATOR_NAMESPACE) === 0; + } + + /** + * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation + * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context. + * + * @param string $type + * @param Context $context + * + * @return Fqsen + * @throws \InvalidArgumentException when type is not a valid FQSEN. + */ + private function resolvePartialStructuralElementName($type, Context $context) + { + $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2); + + $namespaceAliases = $context->getNamespaceAliases(); + + // if the first segment is not an alias; prepend namespace name and return + if (!isset($namespaceAliases[$typeParts[0]])) { + $namespace = $context->getNamespace(); + if ('' !== $namespace) { + $namespace .= self::OPERATOR_NAMESPACE; + } + + return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type); + } + + $typeParts[0] = $namespaceAliases[$typeParts[0]]; + + return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts)); + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Type.php b/vendor/phpdocumentor/type-resolver/src/Type.php new file mode 100644 index 0000000000..33ca559587 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Type.php @@ -0,0 +1,18 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +interface Type +{ + public function __toString(); +} diff --git a/vendor/phpdocumentor/type-resolver/src/TypeResolver.php b/vendor/phpdocumentor/type-resolver/src/TypeResolver.php new file mode 100644 index 0000000000..08b2a5f8aa --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/TypeResolver.php @@ -0,0 +1,298 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection; + +use phpDocumentor\Reflection\Types\Array_; +use phpDocumentor\Reflection\Types\Compound; +use phpDocumentor\Reflection\Types\Context; +use phpDocumentor\Reflection\Types\Iterable_; +use phpDocumentor\Reflection\Types\Nullable; +use phpDocumentor\Reflection\Types\Object_; + +final class TypeResolver +{ + /** @var string Definition of the ARRAY operator for types */ + const OPERATOR_ARRAY = '[]'; + + /** @var string Definition of the NAMESPACE operator in PHP */ + const OPERATOR_NAMESPACE = '\\'; + + /** @var string[] List of recognized keywords and unto which Value Object they map */ + private $keywords = array( + 'string' => Types\String_::class, + 'int' => Types\Integer::class, + 'integer' => Types\Integer::class, + 'bool' => Types\Boolean::class, + 'boolean' => Types\Boolean::class, + 'float' => Types\Float_::class, + 'double' => Types\Float_::class, + 'object' => Object_::class, + 'mixed' => Types\Mixed_::class, + 'array' => Array_::class, + 'resource' => Types\Resource_::class, + 'void' => Types\Void_::class, + 'null' => Types\Null_::class, + 'scalar' => Types\Scalar::class, + 'callback' => Types\Callable_::class, + 'callable' => Types\Callable_::class, + 'false' => Types\Boolean::class, + 'true' => Types\Boolean::class, + 'self' => Types\Self_::class, + '$this' => Types\This::class, + 'static' => Types\Static_::class, + 'parent' => Types\Parent_::class, + 'iterable' => Iterable_::class, + ); + + /** @var FqsenResolver */ + private $fqsenResolver; + + /** + * Initializes this TypeResolver with the means to create and resolve Fqsen objects. + * + * @param FqsenResolver $fqsenResolver + */ + public function __construct(FqsenResolver $fqsenResolver = null) + { + $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver(); + } + + /** + * Analyzes the given type and returns the FQCN variant. + * + * When a type is provided this method checks whether it is not a keyword or + * Fully Qualified Class Name. If so it will use the given namespace and + * aliases to expand the type to a FQCN representation. + * + * This method only works as expected if the namespace and aliases are set; + * no dynamic reflection is being performed here. + * + * @param string $type The relative or absolute type. + * @param Context $context + * + * @uses Context::getNamespace() to determine with what to prefix the type name. + * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be + * replaced with another namespace. + * + * @return Type|null + */ + public function resolve($type, Context $context = null) + { + if (!is_string($type)) { + throw new \InvalidArgumentException( + 'Attempted to resolve type but it appeared not to be a string, received: ' . var_export($type, true) + ); + } + + $type = trim($type); + if (!$type) { + throw new \InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty'); + } + + if ($context === null) { + $context = new Context(''); + } + + switch (true) { + case $this->isNullableType($type): + return $this->resolveNullableType($type, $context); + case $this->isKeyword($type): + return $this->resolveKeyword($type); + case ($this->isCompoundType($type)): + return $this->resolveCompoundType($type, $context); + case $this->isTypedArray($type): + return $this->resolveTypedArray($type, $context); + case $this->isFqsen($type): + return $this->resolveTypedObject($type); + case $this->isPartialStructuralElementName($type): + return $this->resolveTypedObject($type, $context); + // @codeCoverageIgnoreStart + default: + // I haven't got the foggiest how the logic would come here but added this as a defense. + throw new \RuntimeException( + 'Unable to resolve type "' . $type . '", there is no known method to resolve it' + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * Adds a keyword to the list of Keywords and associates it with a specific Value Object. + * + * @param string $keyword + * @param string $typeClassName + * + * @return void + */ + public function addKeyword($keyword, $typeClassName) + { + if (!class_exists($typeClassName)) { + throw new \InvalidArgumentException( + 'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class' + . ' but we could not find the class ' . $typeClassName + ); + } + + if (!in_array(Type::class, class_implements($typeClassName))) { + throw new \InvalidArgumentException( + 'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"' + ); + } + + $this->keywords[$keyword] = $typeClassName; + } + + /** + * Detects whether the given type represents an array. + * + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. + * + * @return bool + */ + private function isTypedArray($type) + { + return substr($type, -2) === self::OPERATOR_ARRAY; + } + + /** + * Detects whether the given type represents a PHPDoc keyword. + * + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. + * + * @return bool + */ + private function isKeyword($type) + { + return in_array(strtolower($type), array_keys($this->keywords), true); + } + + /** + * Detects whether the given type represents a relative structural element name. + * + * @param string $type A relative or absolute type as defined in the phpDocumentor documentation. + * + * @return bool + */ + private function isPartialStructuralElementName($type) + { + return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type); + } + + /** + * Tests whether the given type is a Fully Qualified Structural Element Name. + * + * @param string $type + * + * @return bool + */ + private function isFqsen($type) + { + return strpos($type, self::OPERATOR_NAMESPACE) === 0; + } + + /** + * Tests whether the given type is a compound type (i.e. `string|int`). + * + * @param string $type + * + * @return bool + */ + private function isCompoundType($type) + { + return strpos($type, '|') !== false; + } + + /** + * Test whether the given type is a nullable type (i.e. `?string`) + * + * @param string $type + * + * @return bool + */ + private function isNullableType($type) + { + return $type[0] === '?'; + } + + /** + * Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set. + * + * @param string $type + * @param Context $context + * + * @return Array_ + */ + private function resolveTypedArray($type, Context $context) + { + return new Array_($this->resolve(substr($type, 0, -2), $context)); + } + + /** + * Resolves the given keyword (such as `string`) into a Type object representing that keyword. + * + * @param string $type + * + * @return Type + */ + private function resolveKeyword($type) + { + $className = $this->keywords[strtolower($type)]; + + return new $className(); + } + + /** + * Resolves the given FQSEN string into an FQSEN object. + * + * @param string $type + * @param Context|null $context + * + * @return Object_ + */ + private function resolveTypedObject($type, Context $context = null) + { + return new Object_($this->fqsenResolver->resolve($type, $context)); + } + + /** + * Resolves a compound type (i.e. `string|int`) into the appropriate Type objects or FQSEN. + * + * @param string $type + * @param Context $context + * + * @return Compound + */ + private function resolveCompoundType($type, Context $context) + { + $types = []; + + foreach (explode('|', $type) as $part) { + $types[] = $this->resolve($part, $context); + } + + return new Compound($types); + } + + /** + * Resolve nullable types (i.e. `?string`) into a Nullable type wrapper + * + * @param string $type + * @param Context $context + * + * @return Nullable + */ + private function resolveNullableType($type, Context $context) + { + return new Nullable($this->resolve(ltrim($type, '?'), $context)); + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Array_.php b/vendor/phpdocumentor/type-resolver/src/Types/Array_.php new file mode 100644 index 0000000000..49b7c6ea0c --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Array_.php @@ -0,0 +1,86 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Represents an array type as described in the PSR-5, the PHPDoc Standard. + * + * An array can be represented in two forms: + * + * 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed_'. + * 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a + * type name. + */ +final class Array_ implements Type +{ + /** @var Type */ + private $valueType; + + /** @var Type */ + private $keyType; + + /** + * Initializes this representation of an array with the given Type or Fqsen. + * + * @param Type $valueType + * @param Type $keyType + */ + public function __construct(Type $valueType = null, Type $keyType = null) + { + if ($keyType === null) { + $keyType = new Compound([ new String_(), new Integer() ]); + } + if ($valueType === null) { + $valueType = new Mixed_(); + } + + $this->valueType = $valueType; + $this->keyType = $keyType; + } + + /** + * Returns the type for the keys of this array. + * + * @return Type + */ + public function getKeyType() + { + return $this->keyType; + } + + /** + * Returns the value for the keys of this array. + * + * @return Type + */ + public function getValueType() + { + return $this->valueType; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + if ($this->valueType instanceof Mixed_) { + return 'array'; + } + + return $this->valueType . '[]'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Boolean.php b/vendor/phpdocumentor/type-resolver/src/Types/Boolean.php new file mode 100644 index 0000000000..f82b19e563 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Boolean.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing a Boolean type. + */ +final class Boolean implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'bool'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Callable_.php b/vendor/phpdocumentor/type-resolver/src/Types/Callable_.php new file mode 100644 index 0000000000..68ebfbd064 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Callable_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing a Callable type. + */ +final class Callable_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'callable'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Compound.php b/vendor/phpdocumentor/type-resolver/src/Types/Compound.php new file mode 100644 index 0000000000..be986c317c --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Compound.php @@ -0,0 +1,93 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use ArrayIterator; +use IteratorAggregate; +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing a Compound Type. + * + * A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated + * using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type + * may contain a value with any of the given types. + */ +final class Compound implements Type, IteratorAggregate +{ + /** @var Type[] */ + private $types; + + /** + * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface. + * + * @param Type[] $types + * @throws \InvalidArgumentException when types are not all instance of Type + */ + public function __construct(array $types) + { + foreach ($types as $type) { + if (!$type instanceof Type) { + throw new \InvalidArgumentException('A compound type can only have other types as elements'); + } + } + + $this->types = $types; + } + + /** + * Returns the type at the given index. + * + * @param integer $index + * + * @return Type|null + */ + public function get($index) + { + if (!$this->has($index)) { + return null; + } + + return $this->types[$index]; + } + + /** + * Tests if this compound type has a type with the given index. + * + * @param integer $index + * + * @return bool + */ + public function has($index) + { + return isset($this->types[$index]); + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return implode('|', $this->types); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new ArrayIterator($this->types); + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Context.php b/vendor/phpdocumentor/type-resolver/src/Types/Context.php new file mode 100644 index 0000000000..4e9ce5a03b --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Context.php @@ -0,0 +1,84 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +/** + * Provides information about the Context in which the DocBlock occurs that receives this context. + * + * A DocBlock does not know of its own accord in which namespace it occurs and which namespace aliases are applicable + * for the block of code in which it is in. This information is however necessary to resolve Class names in tags since + * you can provide a short form or make use of namespace aliases. + * + * The phpDocumentor Reflection component knows how to create this class but if you use the DocBlock parser from your + * own application it is possible to generate a Context class using the ContextFactory; this will analyze the file in + * which an associated class resides for its namespace and imports. + * + * @see ContextFactory::createFromClassReflector() + * @see ContextFactory::createForNamespace() + */ +final class Context +{ + /** @var string The current namespace. */ + private $namespace; + + /** @var array List of namespace aliases => Fully Qualified Namespace. */ + private $namespaceAliases; + + /** + * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN) + * format (without a preceding `\`). + * + * @param string $namespace The namespace where this DocBlock resides in. + * @param array $namespaceAliases List of namespace aliases => Fully Qualified Namespace. + */ + public function __construct($namespace, array $namespaceAliases = []) + { + $this->namespace = ('global' !== $namespace && 'default' !== $namespace) + ? trim((string)$namespace, '\\') + : ''; + + foreach ($namespaceAliases as $alias => $fqnn) { + if ($fqnn[0] === '\\') { + $fqnn = substr($fqnn, 1); + } + if ($fqnn[strlen($fqnn) - 1] === '\\') { + $fqnn = substr($fqnn, 0, -1); + } + + $namespaceAliases[$alias] = $fqnn; + } + + $this->namespaceAliases = $namespaceAliases; + } + + /** + * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent + * the alias for the imported Namespace. + * + * @return string[] + */ + public function getNamespaceAliases() + { + return $this->namespaceAliases; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php b/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php new file mode 100644 index 0000000000..30936a3049 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php @@ -0,0 +1,210 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +/** + * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor. + * + * For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to + * provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to + * Fully Qualified names. + * + * @see Context for more information. + */ +final class ContextFactory +{ + /** The literal used at the end of a use statement. */ + const T_LITERAL_END_OF_USE = ';'; + + /** The literal used between sets of use statements */ + const T_LITERAL_USE_SEPARATOR = ','; + + /** + * Build a Context given a Class Reflection. + * + * @param \Reflector $reflector + * + * @see Context for more information on Contexts. + * + * @return Context + */ + public function createFromReflector(\Reflector $reflector) + { + if (method_exists($reflector, 'getDeclaringClass')) { + $reflector = $reflector->getDeclaringClass(); + } + + $fileName = $reflector->getFileName(); + $namespace = $reflector->getNamespaceName(); + + if (file_exists($fileName)) { + return $this->createForNamespace($namespace, file_get_contents($fileName)); + } + + return new Context($namespace, []); + } + + /** + * Build a Context for a namespace in the provided file contents. + * + * @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes. + * @param string $fileContents the file's contents to retrieve the aliases from with the given namespace. + * + * @see Context for more information on Contexts. + * + * @return Context + */ + public function createForNamespace($namespace, $fileContents) + { + $namespace = trim($namespace, '\\'); + $useStatements = []; + $currentNamespace = ''; + $tokens = new \ArrayIterator(token_get_all($fileContents)); + + while ($tokens->valid()) { + switch ($tokens->current()[0]) { + case T_NAMESPACE: + $currentNamespace = $this->parseNamespace($tokens); + break; + case T_CLASS: + // Fast-forward the iterator through the class so that any + // T_USE tokens found within are skipped - these are not + // valid namespace use statements so should be ignored. + $braceLevel = 0; + $firstBraceFound = false; + while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) { + if ($tokens->current() === '{' + || $tokens->current()[0] === T_CURLY_OPEN + || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) { + if (!$firstBraceFound) { + $firstBraceFound = true; + } + $braceLevel++; + } + + if ($tokens->current() === '}') { + $braceLevel--; + } + $tokens->next(); + } + break; + case T_USE: + if ($currentNamespace === $namespace) { + $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens)); + } + break; + } + $tokens->next(); + } + + return new Context($namespace, $useStatements); + } + + /** + * Deduce the name from tokens when we are at the T_NAMESPACE token. + * + * @param \ArrayIterator $tokens + * + * @return string + */ + private function parseNamespace(\ArrayIterator $tokens) + { + // skip to the first string or namespace separator + $this->skipToNextStringOrNamespaceSeparator($tokens); + + $name = ''; + while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) + ) { + $name .= $tokens->current()[1]; + $tokens->next(); + } + + return $name; + } + + /** + * Deduce the names of all imports when we are at the T_USE token. + * + * @param \ArrayIterator $tokens + * + * @return string[] + */ + private function parseUseStatement(\ArrayIterator $tokens) + { + $uses = []; + $continue = true; + + while ($continue) { + $this->skipToNextStringOrNamespaceSeparator($tokens); + + list($alias, $fqnn) = $this->extractUseStatement($tokens); + $uses[$alias] = $fqnn; + if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) { + $continue = false; + } + } + + return $uses; + } + + /** + * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token. + * + * @param \ArrayIterator $tokens + * + * @return void + */ + private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens) + { + while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) { + $tokens->next(); + } + } + + /** + * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of + * a USE statement yet. + * + * @param \ArrayIterator $tokens + * + * @return string + */ + private function extractUseStatement(\ArrayIterator $tokens) + { + $result = ['']; + while ($tokens->valid() + && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR) + && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE) + ) { + if ($tokens->current()[0] === T_AS) { + $result[] = ''; + } + if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) { + $result[count($result) - 1] .= $tokens->current()[1]; + } + $tokens->next(); + } + + if (count($result) == 1) { + $backslashPos = strrpos($result[0], '\\'); + + if (false !== $backslashPos) { + $result[] = substr($result[0], $backslashPos + 1); + } else { + $result[] = $result[0]; + } + } + + return array_reverse($result); + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Float_.php b/vendor/phpdocumentor/type-resolver/src/Types/Float_.php new file mode 100644 index 0000000000..e58d8966e0 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Float_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing a Float. + */ +final class Float_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'float'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Integer.php b/vendor/phpdocumentor/type-resolver/src/Types/Integer.php new file mode 100644 index 0000000000..be4555ef67 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Integer.php @@ -0,0 +1,28 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +final class Integer implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'int'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php b/vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php new file mode 100644 index 0000000000..0cbf48f7b9 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing iterable type + */ +final class Iterable_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'iterable'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Mixed_.php b/vendor/phpdocumentor/type-resolver/src/Types/Mixed_.php new file mode 100644 index 0000000000..c1c165f476 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Mixed_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing an unknown, or mixed, type. + */ +final class Mixed_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'mixed'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Null_.php b/vendor/phpdocumentor/type-resolver/src/Types/Null_.php new file mode 100644 index 0000000000..203b422719 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Null_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing a null value or type. + */ +final class Null_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'null'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Nullable.php b/vendor/phpdocumentor/type-resolver/src/Types/Nullable.php new file mode 100644 index 0000000000..3c6d1b1355 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Nullable.php @@ -0,0 +1,56 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing a nullable type. The real type is wrapped. + */ +final class Nullable implements Type +{ + /** + * @var Type + */ + private $realType; + + /** + * Initialises this nullable type using the real type embedded + * + * @param Type $realType + */ + public function __construct(Type $realType) + { + $this->realType = $realType; + } + + /** + * Provide access to the actual type directly, if needed. + * + * @return Type + */ + public function getActualType() + { + return $this->realType; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return '?' . $this->realType->__toString(); + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Object_.php b/vendor/phpdocumentor/type-resolver/src/Types/Object_.php new file mode 100644 index 0000000000..389f7c70fa --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Object_.php @@ -0,0 +1,71 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Fqsen; +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing an object. + * + * An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN, + * pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects + * in general. + */ +final class Object_ implements Type +{ + /** @var Fqsen|null */ + private $fqsen; + + /** + * Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'. + * + * @param Fqsen $fqsen + * @throws \InvalidArgumentException when provided $fqsen is not a valid type. + */ + public function __construct(Fqsen $fqsen = null) + { + if (strpos((string)$fqsen, '::') !== false || strpos((string)$fqsen, '()') !== false) { + throw new \InvalidArgumentException( + 'Object types can only refer to a class, interface or trait but a method, function, constant or ' + . 'property was received: ' . (string)$fqsen + ); + } + + $this->fqsen = $fqsen; + } + + /** + * Returns the FQSEN associated with this object. + * + * @return Fqsen|null + */ + public function getFqsen() + { + return $this->fqsen; + } + + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + if ($this->fqsen) { + return (string)$this->fqsen; + } + + return 'object'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Parent_.php b/vendor/phpdocumentor/type-resolver/src/Types/Parent_.php new file mode 100644 index 0000000000..aabdbfb31d --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Parent_.php @@ -0,0 +1,33 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the 'parent' type. + * + * Parent, as a Type, represents the parent class of class in which the associated element was defined. + */ +final class Parent_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'parent'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Resource_.php b/vendor/phpdocumentor/type-resolver/src/Types/Resource_.php new file mode 100644 index 0000000000..a1b613dc51 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Resource_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the 'resource' Type. + */ +final class Resource_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'resource'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Scalar.php b/vendor/phpdocumentor/type-resolver/src/Types/Scalar.php new file mode 100644 index 0000000000..1e2a660299 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Scalar.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the 'scalar' pseudo-type, which is either a string, integer, float or boolean. + */ +final class Scalar implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'scalar'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Self_.php b/vendor/phpdocumentor/type-resolver/src/Types/Self_.php new file mode 100644 index 0000000000..1ba3fc5a50 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Self_.php @@ -0,0 +1,33 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the 'self' type. + * + * Self, as a Type, represents the class in which the associated element was defined. + */ +final class Self_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'self'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Static_.php b/vendor/phpdocumentor/type-resolver/src/Types/Static_.php new file mode 100644 index 0000000000..9eb67299cb --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Static_.php @@ -0,0 +1,38 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the 'static' type. + * + * Self, as a Type, represents the class in which the associated element was called. This differs from self as self does + * not take inheritance into account but static means that the return type is always that of the class of the called + * element. + * + * See the documentation on late static binding in the PHP Documentation for more information on the difference between + * static and self. + */ +final class Static_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'static'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/String_.php b/vendor/phpdocumentor/type-resolver/src/Types/String_.php new file mode 100644 index 0000000000..8db596857d --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/String_.php @@ -0,0 +1,31 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the type 'string'. + */ +final class String_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'string'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/This.php b/vendor/phpdocumentor/type-resolver/src/Types/This.php new file mode 100644 index 0000000000..c098a939ba --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/This.php @@ -0,0 +1,34 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the '$this' pseudo-type. + * + * $this, as a Type, represents the instance of the class associated with the element as it was called. $this is + * commonly used when documenting fluent interfaces since it represents that the same object is returned. + */ +final class This implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return '$this'; + } +} diff --git a/vendor/phpdocumentor/type-resolver/src/Types/Void_.php b/vendor/phpdocumentor/type-resolver/src/Types/Void_.php new file mode 100644 index 0000000000..3d1be272c3 --- /dev/null +++ b/vendor/phpdocumentor/type-resolver/src/Types/Void_.php @@ -0,0 +1,34 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace phpDocumentor\Reflection\Types; + +use phpDocumentor\Reflection\Type; + +/** + * Value Object representing the pseudo-type 'void'. + * + * Void is generally only used when working with return types as it signifies that the method intentionally does not + * return any value. + */ +final class Void_ implements Type +{ + /** + * Returns a rendered output of the Type as it would be used in a DocBlock. + * + * @return string + */ + public function __toString() + { + return 'void'; + } +} diff --git a/vendor/phpspec/prophecy/CHANGES.md b/vendor/phpspec/prophecy/CHANGES.md new file mode 100644 index 0000000000..0dadd52a83 --- /dev/null +++ b/vendor/phpspec/prophecy/CHANGES.md @@ -0,0 +1,194 @@ +1.7.5 / 2018/04/18 +================== + +* Allow sebastian/comparator ^3.0 (@sebastianbergmann) + +1.7.4 / 2018/02/11 +================== + +* Fix issues with PHP 7.2 (thanks @greg0ire) +* Support object type hints in PHP 7.2 (thanks @@jansvoboda11) + +1.7.3 / 2017/11/24 +================== + +* Fix SplInfo ClassPatch to work with Symfony 4 (Thanks @gnugat) + +1.7.2 / 2017-10-04 +================== + +* Reverted "check method predictions only once" due to it breaking Spies + +1.7.1 / 2017-10-03 +================== + +* Allow PHP5 keywords methods generation on PHP7 (thanks @bycosta) +* Allow reflection-docblock v4 (thanks @GrahamCampbell) +* Check method predictions only once (thanks @dontub) +* Escape file path sent to \SplFileObjectConstructor when running on Windows (thanks @danmartin-epiphany) + +1.7.0 / 2017-03-02 +================== + +* Add full PHP 7.1 Support (thanks @prolic) +* Allow `sebastian/comparator ^2.0` (thanks @sebastianbergmann) +* Allow `sebastian/recursion-context ^3.0` (thanks @sebastianbergmann) +* Allow `\Error` instances in `ThrowPromise` (thanks @jameshalsall) +* Support `phpspec/phpspect ^3.2` (thanks @Sam-Burns) +* Fix failing builds (thanks @Sam-Burns) + +1.6.2 / 2016-11-21 +================== + +* Added support for detecting @method on interfaces that the class itself implements, or when the stubbed class is an interface itself (thanks @Seldaek) +* Added support for sebastian/recursion-context 2 (thanks @sebastianbergmann) +* Added testing on PHP 7.1 on Travis (thanks @danizord) +* Fixed the usage of the phpunit comparator (thanks @Anyqax) + +1.6.1 / 2016-06-07 +================== + + * Ignored empty method names in invalid `@method` phpdoc + * Fixed the mocking of SplFileObject + * Added compatibility with phpdocumentor/reflection-docblock 3 + +1.6.0 / 2016-02-15 +================== + + * Add Variadics support (thanks @pamil) + * Add ProphecyComparator for comparing objects that need revealing (thanks @jon-acker) + * Add ApproximateValueToken (thanks @dantleech) + * Add support for 'self' and 'parent' return type (thanks @bendavies) + * Add __invoke to allowed reflectable methods list (thanks @ftrrtf) + * Updated ExportUtil to reflect the latest changes by Sebastian (thanks @jakari) + * Specify the required php version for composer (thanks @jakzal) + * Exclude 'args' in the generated backtrace (thanks @oradwell) + * Fix code generation for scalar parameters (thanks @trowski) + * Fix missing sprintf in InvalidArgumentException __construct call (thanks @emmanuelballery) + * Fix phpdoc for magic methods (thanks @Tobion) + * Fix PhpDoc for interfaces usage (thanks @ImmRanneft) + * Prevent final methods from being manually extended (thanks @kamioftea) + * Enhance exception for invalid argument to ThrowPromise (thanks @Tobion) + +1.5.0 / 2015-04-27 +================== + + * Add support for PHP7 scalar type hints (thanks @trowski) + * Add support for PHP7 return types (thanks @trowski) + * Update internal test suite to support PHP7 + +1.4.1 / 2015-04-27 +================== + + * Fixed bug in closure-based argument tokens (#181) + +1.4.0 / 2015-03-27 +================== + + * Fixed errors in return type phpdocs (thanks @sobit) + * Fixed stringifying of hash containing one value (thanks @avant1) + * Improved clarity of method call expectation exception (thanks @dantleech) + * Add ability to specify which argument is returned in willReturnArgument (thanks @coderbyheart) + * Add more information to MethodNotFound exceptions (thanks @ciaranmcnulty) + * Support for mocking classes with methods that return references (thanks @edsonmedina) + * Improved object comparison (thanks @whatthejeff) + * Adopted '^' in composer dependencies (thanks @GrahamCampbell) + * Fixed non-typehinted arguments being treated as optional (thanks @whatthejeff) + * Magic methods are now filtered for keywords (thanks @seagoj) + * More readable errors for failure when expecting single calls (thanks @dantleech) + +1.3.1 / 2014-11-17 +================== + + * Fix the edge case when failed predictions weren't recorded for `getCheckedPredictions()` + +1.3.0 / 2014-11-14 +================== + + * Add a way to get checked predictions with `MethodProphecy::getCheckedPredictions()` + * Fix HHVM compatibility + * Remove dead code (thanks @stof) + * Add support for DirectoryIterators (thanks @shanethehat) + +1.2.0 / 2014-07-18 +================== + + * Added support for doubling magic methods documented in the class phpdoc (thanks @armetiz) + * Fixed a segfault appearing in some cases (thanks @dmoreaulf) + * Fixed the doubling of methods with typehints on non-existent classes (thanks @gquemener) + * Added support for internal classes using keywords as method names (thanks @milan) + * Added IdenticalValueToken and Argument::is (thanks @florianv) + * Removed the usage of scalar typehints in HHVM as HHVM 3 does not support them anymore in PHP code (thanks @whatthejeff) + +1.1.2 / 2014-01-24 +================== + + * Spy automatically promotes spied method call to an expected one + +1.1.1 / 2014-01-15 +================== + + * Added support for HHVM + +1.1.0 / 2014-01-01 +================== + + * Changed the generated class names to use a static counter instead of a random number + * Added a clss patch for ReflectionClass::newInstance to make its argument optional consistently (thanks @docteurklein) + * Fixed mirroring of classes with typehints on non-existent classes (thanks @docteurklein) + * Fixed the support of array callables in CallbackPromise and CallbackPrediction (thanks @ciaranmcnulty) + * Added support for properties in ObjectStateToken (thanks @adrienbrault) + * Added support for mocking classes with a final constructor (thanks @ciaranmcnulty) + * Added ArrayEveryEntryToken and Argument::withEveryEntry() (thanks @adrienbrault) + * Added an exception when trying to prophesize on a final method instead of ignoring silently (thanks @docteurklein) + * Added StringContainToken and Argument::containingString() (thanks @peterjmit) + * Added ``shouldNotHaveBeenCalled`` on the MethodProphecy (thanks @ciaranmcnulty) + * Fixed the comparison of objects in ExactValuetoken (thanks @sstok) + * Deprecated ``shouldNotBeenCalled`` in favor of ``shouldNotHaveBeenCalled`` + +1.0.4 / 2013-08-10 +================== + + * Better randomness for generated class names (thanks @sstok) + * Add support for interfaces into TypeToken and Argument::type() (thanks @sstok) + * Add support for old-style (method name === class name) constructors (thanks @l310 for report) + +1.0.3 / 2013-07-04 +================== + + * Support callable typehints (thanks @stof) + * Do not attempt to autoload arrays when generating code (thanks @MarcoDeBortoli) + * New ArrayEntryToken (thanks @kagux) + +1.0.2 / 2013-05-19 +================== + + * Logical `AND` token added (thanks @kagux) + * Logical `NOT` token added (thanks @kagux) + * Add support for setting custom constructor arguments + * Properly stringify hashes + * Record calls that throw exceptions + * Migrate spec suite to PhpSpec 2.0 + +1.0.1 / 2013-04-30 +================== + + * Fix broken UnexpectedCallException message + * Trim AggregateException message + +1.0.0 / 2013-04-29 +================== + + * Improve exception messages + +1.0.0-BETA2 / 2013-04-03 +======================== + + * Add more debug information to CallTimes and Call prediction exception messages + * Fix MethodNotFoundException wrong namespace (thanks @gunnarlium) + * Fix some typos in the exception messages (thanks @pborreli) + +1.0.0-BETA1 / 2013-03-25 +======================== + + * Initial release diff --git a/vendor/phpspec/prophecy/LICENSE b/vendor/phpspec/prophecy/LICENSE new file mode 100644 index 0000000000..c8b364711a --- /dev/null +++ b/vendor/phpspec/prophecy/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013 Konstantin Kudryashov + Marcello Duarte + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/phpspec/prophecy/README.md b/vendor/phpspec/prophecy/README.md new file mode 100644 index 0000000000..b190d43e19 --- /dev/null +++ b/vendor/phpspec/prophecy/README.md @@ -0,0 +1,391 @@ +# Prophecy + +[![Stable release](https://poser.pugx.org/phpspec/prophecy/version.svg)](https://packagist.org/packages/phpspec/prophecy) +[![Build Status](https://travis-ci.org/phpspec/prophecy.svg?branch=master)](https://travis-ci.org/phpspec/prophecy) + +Prophecy is a highly opinionated yet very powerful and flexible PHP object mocking +framework. Though initially it was created to fulfil phpspec2 needs, it is flexible +enough to be used inside any testing framework out there with minimal effort. + +## A simple example + +```php +prophet->prophesize('App\Security\Hasher'); + $user = new App\Entity\User($hasher->reveal()); + + $hasher->generateHash($user, 'qwerty')->willReturn('hashed_pass'); + + $user->setPassword('qwerty'); + + $this->assertEquals('hashed_pass', $user->getPassword()); + } + + protected function setup() + { + $this->prophet = new \Prophecy\Prophet; + } + + protected function tearDown() + { + $this->prophet->checkPredictions(); + } +} +``` + +## Installation + +### Prerequisites + +Prophecy requires PHP 5.3.3 or greater. + +### Setup through composer + +First, add Prophecy to the list of dependencies inside your `composer.json`: + +```json +{ + "require-dev": { + "phpspec/prophecy": "~1.0" + } +} +``` + +Then simply install it with composer: + +```bash +$> composer install --prefer-dist +``` + +You can read more about Composer on its [official webpage](http://getcomposer.org). + +## How to use it + +First of all, in Prophecy every word has a logical meaning, even the name of the library +itself (Prophecy). When you start feeling that, you'll become very fluid with this +tool. + +For example, Prophecy has been named that way because it concentrates on describing the future +behavior of objects with very limited knowledge about them. But as with any other prophecy, +those object prophecies can't create themselves - there should be a Prophet: + +```php +$prophet = new Prophecy\Prophet; +``` + +The Prophet creates prophecies by *prophesizing* them: + +```php +$prophecy = $prophet->prophesize(); +``` + +The result of the `prophesize()` method call is a new object of class `ObjectProphecy`. Yes, +that's your specific object prophecy, which describes how your object would behave +in the near future. But first, you need to specify which object you're talking about, +right? + +```php +$prophecy->willExtend('stdClass'); +$prophecy->willImplement('SessionHandlerInterface'); +``` + +There are 2 interesting calls - `willExtend` and `willImplement`. The first one tells +object prophecy that our object should extend specific class, the second one says that +it should implement some interface. Obviously, objects in PHP can implement multiple +interfaces, but extend only one parent class. + +### Dummies + +Ok, now we have our object prophecy. What can we do with it? First of all, we can get +our object *dummy* by revealing its prophecy: + +```php +$dummy = $prophecy->reveal(); +``` + +The `$dummy` variable now holds a special dummy object. Dummy objects are objects that extend +and/or implement preset classes/interfaces by overriding all their public methods. The key +point about dummies is that they do not hold any logic - they just do nothing. Any method +of the dummy will always return `null` and the dummy will never throw any exceptions. +Dummy is your friend if you don't care about the actual behavior of this double and just need +a token object to satisfy a method typehint. + +You need to understand one thing - a dummy is not a prophecy. Your object prophecy is still +assigned to `$prophecy` variable and in order to manipulate with your expectations, you +should work with it. `$dummy` is a dummy - a simple php object that tries to fulfil your +prophecy. + +### Stubs + +Ok, now we know how to create basic prophecies and reveal dummies from them. That's +awesome if we don't care about our _doubles_ (objects that reflect originals) +interactions. If we do, we need to use *stubs* or *mocks*. + +A stub is an object double, which doesn't have any expectations about the object behavior, +but when put in specific environment, behaves in specific way. Ok, I know, it's cryptic, +but bear with me for a minute. Simply put, a stub is a dummy, which depending on the called +method signature does different things (has logic). To create stubs in Prophecy: + +```php +$prophecy->read('123')->willReturn('value'); +``` + +Oh wow. We've just made an arbitrary call on the object prophecy? Yes, we did. And this +call returned us a new object instance of class `MethodProphecy`. Yep, that's a specific +method with arguments prophecy. Method prophecies give you the ability to create method +promises or predictions. We'll talk about method predictions later in the _Mocks_ section. + +#### Promises + +Promises are logical blocks, that represent your fictional methods in prophecy terms +and they are handled by the `MethodProphecy::will(PromiseInterface $promise)` method. +As a matter of fact, the call that we made earlier (`willReturn('value')`) is a simple +shortcut to: + +```php +$prophecy->read('123')->will(new Prophecy\Promise\ReturnPromise(array('value'))); +``` + +This promise will cause any call to our double's `read()` method with exactly one +argument - `'123'` to always return `'value'`. But that's only for this +promise, there's plenty others you can use: + +- `ReturnPromise` or `->willReturn(1)` - returns a value from a method call +- `ReturnArgumentPromise` or `->willReturnArgument($index)` - returns the nth method argument from call +- `ThrowPromise` or `->willThrow($exception)` - causes the method to throw specific exception +- `CallbackPromise` or `->will($callback)` - gives you a quick way to define your own custom logic + +Keep in mind, that you can always add even more promises by implementing +`Prophecy\Promise\PromiseInterface`. + +#### Method prophecies idempotency + +Prophecy enforces same method prophecies and, as a consequence, same promises and +predictions for the same method calls with the same arguments. This means: + +```php +$methodProphecy1 = $prophecy->read('123'); +$methodProphecy2 = $prophecy->read('123'); +$methodProphecy3 = $prophecy->read('321'); + +$methodProphecy1 === $methodProphecy2; +$methodProphecy1 !== $methodProphecy3; +``` + +That's interesting, right? Now you might ask me how would you define more complex +behaviors where some method call changes behavior of others. In PHPUnit or Mockery +you do that by predicting how many times your method will be called. In Prophecy, +you'll use promises for that: + +```php +$user->getName()->willReturn(null); + +// For PHP 5.4 +$user->setName('everzet')->will(function () { + $this->getName()->willReturn('everzet'); +}); + +// For PHP 5.3 +$user->setName('everzet')->will(function ($args, $user) { + $user->getName()->willReturn('everzet'); +}); + +// Or +$user->setName('everzet')->will(function ($args) use ($user) { + $user->getName()->willReturn('everzet'); +}); +``` + +And now it doesn't matter how many times or in which order your methods are called. +What matters is their behaviors and how well you faked it. + +#### Arguments wildcarding + +The previous example is awesome (at least I hope it is for you), but that's not +optimal enough. We hardcoded `'everzet'` in our expectation. Isn't there a better +way? In fact there is, but it involves understanding what this `'everzet'` +actually is. + +You see, even if method arguments used during method prophecy creation look +like simple method arguments, in reality they are not. They are argument token +wildcards. As a matter of fact, `->setName('everzet')` looks like a simple call just +because Prophecy automatically transforms it under the hood into: + +```php +$user->setName(new Prophecy\Argument\Token\ExactValueToken('everzet')); +``` + +Those argument tokens are simple PHP classes, that implement +`Prophecy\Argument\Token\TokenInterface` and tell Prophecy how to compare real arguments +with your expectations. And yes, those classnames are damn big. That's why there's a +shortcut class `Prophecy\Argument`, which you can use to create tokens like that: + +```php +use Prophecy\Argument; + +$user->setName(Argument::exact('everzet')); +``` + +`ExactValueToken` is not very useful in our case as it forced us to hardcode the username. +That's why Prophecy comes bundled with a bunch of other tokens: + +- `IdenticalValueToken` or `Argument::is($value)` - checks that the argument is identical to a specific value +- `ExactValueToken` or `Argument::exact($value)` - checks that the argument matches a specific value +- `TypeToken` or `Argument::type($typeOrClass)` - checks that the argument matches a specific type or + classname +- `ObjectStateToken` or `Argument::which($method, $value)` - checks that the argument method returns + a specific value +- `CallbackToken` or `Argument::that(callback)` - checks that the argument matches a custom callback +- `AnyValueToken` or `Argument::any()` - matches any argument +- `AnyValuesToken` or `Argument::cetera()` - matches any arguments to the rest of the signature +- `StringContainsToken` or `Argument::containingString($value)` - checks that the argument contains a specific string value + +And you can add even more by implementing `TokenInterface` with your own custom classes. + +So, let's refactor our initial `{set,get}Name()` logic with argument tokens: + +```php +use Prophecy\Argument; + +$user->getName()->willReturn(null); + +// For PHP 5.4 +$user->setName(Argument::type('string'))->will(function ($args) { + $this->getName()->willReturn($args[0]); +}); + +// For PHP 5.3 +$user->setName(Argument::type('string'))->will(function ($args, $user) { + $user->getName()->willReturn($args[0]); +}); + +// Or +$user->setName(Argument::type('string'))->will(function ($args) use ($user) { + $user->getName()->willReturn($args[0]); +}); +``` + +That's it. Now our `{set,get}Name()` prophecy will work with any string argument provided to it. +We've just described how our stub object should behave, even though the original object could have +no behavior whatsoever. + +One last bit about arguments now. You might ask, what happens in case of: + +```php +use Prophecy\Argument; + +$user->getName()->willReturn(null); + +// For PHP 5.4 +$user->setName(Argument::type('string'))->will(function ($args) { + $this->getName()->willReturn($args[0]); +}); + +// For PHP 5.3 +$user->setName(Argument::type('string'))->will(function ($args, $user) { + $user->getName()->willReturn($args[0]); +}); + +// Or +$user->setName(Argument::type('string'))->will(function ($args) use ($user) { + $user->getName()->willReturn($args[0]); +}); + +$user->setName(Argument::any())->will(function () { +}); +``` + +Nothing. Your stub will continue behaving the way it did before. That's because of how +arguments wildcarding works. Every argument token type has a different score level, which +wildcard then uses to calculate the final arguments match score and use the method prophecy +promise that has the highest score. In this case, `Argument::type()` in case of success +scores `5` and `Argument::any()` scores `3`. So the type token wins, as does the first +`setName()` method prophecy and its promise. The simple rule of thumb - more precise token +always wins. + +#### Getting stub objects + +Ok, now we know how to define our prophecy method promises, let's get our stub from +it: + +```php +$stub = $prophecy->reveal(); +``` + +As you might see, the only difference between how we get dummies and stubs is that with +stubs we describe every object conversation instead of just agreeing with `null` returns +(object being *dummy*). As a matter of fact, after you define your first promise +(method call), Prophecy will force you to define all the communications - it throws +the `UnexpectedCallException` for any call you didn't describe with object prophecy before +calling it on a stub. + +### Mocks + +Now we know how to define doubles without behavior (dummies) and doubles with behavior, but +no expectations (stubs). What's left is doubles for which we have some expectations. These +are called mocks and in Prophecy they look almost exactly the same as stubs, except that +they define *predictions* instead of *promises* on method prophecies: + +```php +$entityManager->flush()->shouldBeCalled(); +``` + +#### Predictions + +The `shouldBeCalled()` method here assigns `CallPrediction` to our method prophecy. +Predictions are a delayed behavior check for your prophecies. You see, during the entire lifetime +of your doubles, Prophecy records every single call you're making against it inside your +code. After that, Prophecy can use this collected information to check if it matches defined +predictions. You can assign predictions to method prophecies using the +`MethodProphecy::should(PredictionInterface $prediction)` method. As a matter of fact, +the `shouldBeCalled()` method we used earlier is just a shortcut to: + +```php +$entityManager->flush()->should(new Prophecy\Prediction\CallPrediction()); +``` + +It checks if your method of interest (that matches both the method name and the arguments wildcard) +was called 1 or more times. If the prediction failed then it throws an exception. When does this +check happen? Whenever you call `checkPredictions()` on the main Prophet object: + +```php +$prophet->checkPredictions(); +``` + +In PHPUnit, you would want to put this call into the `tearDown()` method. If no predictions +are defined, it would do nothing. So it won't harm to call it after every test. + +There are plenty more predictions you can play with: + +- `CallPrediction` or `shouldBeCalled()` - checks that the method has been called 1 or more times +- `NoCallsPrediction` or `shouldNotBeCalled()` - checks that the method has not been called +- `CallTimesPrediction` or `shouldBeCalledTimes($count)` - checks that the method has been called + `$count` times +- `CallbackPrediction` or `should($callback)` - checks the method against your own custom callback + +Of course, you can always create your own custom prediction any time by implementing +`PredictionInterface`. + +### Spies + +The last bit of awesomeness in Prophecy is out-of-the-box spies support. As I said in the previous +section, Prophecy records every call made during the double's entire lifetime. This means +you don't need to record predictions in order to check them. You can also do it +manually by using the `MethodProphecy::shouldHave(PredictionInterface $prediction)` method: + +```php +$em = $prophet->prophesize('Doctrine\ORM\EntityManager'); + +$controller->createUser($em->reveal()); + +$em->flush()->shouldHaveBeenCalled(); +``` + +Such manipulation with doubles is called spying. And with Prophecy it just works. diff --git a/vendor/phpspec/prophecy/composer.json b/vendor/phpspec/prophecy/composer.json new file mode 100644 index 0000000000..bb3d3726aa --- /dev/null +++ b/vendor/phpspec/prophecy/composer.json @@ -0,0 +1,50 @@ +{ + "name": "phpspec/prophecy", + "description": "Highly opinionated mocking framework for PHP 5.3+", + "keywords": ["Mock", "Stub", "Dummy", "Double", "Fake", "Spy"], + "homepage": "https://github.com/phpspec/prophecy", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + + "require": { + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "doctrine/instantiator": "^1.0.2", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + + "autoload-dev": { + "psr-4": { + "Fixtures\\Prophecy\\": "fixtures" + } + }, + + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument.php b/vendor/phpspec/prophecy/src/Prophecy/Argument.php new file mode 100644 index 0000000000..fde6aa9000 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument.php @@ -0,0 +1,212 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy; + +use Prophecy\Argument\Token; + +/** + * Argument tokens shortcuts. + * + * @author Konstantin Kudryashov + */ +class Argument +{ + /** + * Checks that argument is exact value or object. + * + * @param mixed $value + * + * @return Token\ExactValueToken + */ + public static function exact($value) + { + return new Token\ExactValueToken($value); + } + + /** + * Checks that argument is of specific type or instance of specific class. + * + * @param string $type Type name (`integer`, `string`) or full class name + * + * @return Token\TypeToken + */ + public static function type($type) + { + return new Token\TypeToken($type); + } + + /** + * Checks that argument object has specific state. + * + * @param string $methodName + * @param mixed $value + * + * @return Token\ObjectStateToken + */ + public static function which($methodName, $value) + { + return new Token\ObjectStateToken($methodName, $value); + } + + /** + * Checks that argument matches provided callback. + * + * @param callable $callback + * + * @return Token\CallbackToken + */ + public static function that($callback) + { + return new Token\CallbackToken($callback); + } + + /** + * Matches any single value. + * + * @return Token\AnyValueToken + */ + public static function any() + { + return new Token\AnyValueToken; + } + + /** + * Matches all values to the rest of the signature. + * + * @return Token\AnyValuesToken + */ + public static function cetera() + { + return new Token\AnyValuesToken; + } + + /** + * Checks that argument matches all tokens + * + * @param mixed ... a list of tokens + * + * @return Token\LogicalAndToken + */ + public static function allOf() + { + return new Token\LogicalAndToken(func_get_args()); + } + + /** + * Checks that argument array or countable object has exact number of elements. + * + * @param integer $value array elements count + * + * @return Token\ArrayCountToken + */ + public static function size($value) + { + return new Token\ArrayCountToken($value); + } + + /** + * Checks that argument array contains (key, value) pair + * + * @param mixed $key exact value or token + * @param mixed $value exact value or token + * + * @return Token\ArrayEntryToken + */ + public static function withEntry($key, $value) + { + return new Token\ArrayEntryToken($key, $value); + } + + /** + * Checks that arguments array entries all match value + * + * @param mixed $value + * + * @return Token\ArrayEveryEntryToken + */ + public static function withEveryEntry($value) + { + return new Token\ArrayEveryEntryToken($value); + } + + /** + * Checks that argument array contains value + * + * @param mixed $value + * + * @return Token\ArrayEntryToken + */ + public static function containing($value) + { + return new Token\ArrayEntryToken(self::any(), $value); + } + + /** + * Checks that argument array has key + * + * @param mixed $key exact value or token + * + * @return Token\ArrayEntryToken + */ + public static function withKey($key) + { + return new Token\ArrayEntryToken($key, self::any()); + } + + /** + * Checks that argument does not match the value|token. + * + * @param mixed $value either exact value or argument token + * + * @return Token\LogicalNotToken + */ + public static function not($value) + { + return new Token\LogicalNotToken($value); + } + + /** + * @param string $value + * + * @return Token\StringContainsToken + */ + public static function containingString($value) + { + return new Token\StringContainsToken($value); + } + + /** + * Checks that argument is identical value. + * + * @param mixed $value + * + * @return Token\IdenticalValueToken + */ + public static function is($value) + { + return new Token\IdenticalValueToken($value); + } + + /** + * Check that argument is same value when rounding to the + * given precision. + * + * @param float $value + * @param float $precision + * + * @return Token\ApproximateValueToken + */ + public static function approximate($value, $precision = 0) + { + return new Token\ApproximateValueToken($value, $precision); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php new file mode 100644 index 0000000000..a088f21d21 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php @@ -0,0 +1,101 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument; + +/** + * Arguments wildcarding. + * + * @author Konstantin Kudryashov + */ +class ArgumentsWildcard +{ + /** + * @var Token\TokenInterface[] + */ + private $tokens = array(); + private $string; + + /** + * Initializes wildcard. + * + * @param array $arguments Array of argument tokens or values + */ + public function __construct(array $arguments) + { + foreach ($arguments as $argument) { + if (!$argument instanceof Token\TokenInterface) { + $argument = new Token\ExactValueToken($argument); + } + + $this->tokens[] = $argument; + } + } + + /** + * Calculates wildcard match score for provided arguments. + * + * @param array $arguments + * + * @return false|int False OR integer score (higher - better) + */ + public function scoreArguments(array $arguments) + { + if (0 == count($arguments) && 0 == count($this->tokens)) { + return 1; + } + + $arguments = array_values($arguments); + $totalScore = 0; + foreach ($this->tokens as $i => $token) { + $argument = isset($arguments[$i]) ? $arguments[$i] : null; + if (1 >= $score = $token->scoreArgument($argument)) { + return false; + } + + $totalScore += $score; + + if (true === $token->isLast()) { + return $totalScore; + } + } + + if (count($arguments) > count($this->tokens)) { + return false; + } + + return $totalScore; + } + + /** + * Returns string representation for wildcard. + * + * @return string + */ + public function __toString() + { + if (null === $this->string) { + $this->string = implode(', ', array_map(function ($token) { + return (string) $token; + }, $this->tokens)); + } + + return $this->string; + } + + /** + * @return array + */ + public function getTokens() + { + return $this->tokens; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php new file mode 100644 index 0000000000..50988112c5 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php @@ -0,0 +1,52 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Any single value token. + * + * @author Konstantin Kudryashov + */ +class AnyValueToken implements TokenInterface +{ + /** + * Always scores 3 for any argument. + * + * @param $argument + * + * @return int + */ + public function scoreArgument($argument) + { + return 3; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return '*'; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php new file mode 100644 index 0000000000..f76b17bc0b --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php @@ -0,0 +1,52 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Any values token. + * + * @author Konstantin Kudryashov + */ +class AnyValuesToken implements TokenInterface +{ + /** + * Always scores 2 for any argument. + * + * @param $argument + * + * @return int + */ + public function scoreArgument($argument) + { + return 2; + } + + /** + * Returns true to stop wildcard from processing other tokens. + * + * @return bool + */ + public function isLast() + { + return true; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return '* [, ...]'; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php new file mode 100644 index 0000000000..d4918b1ad8 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php @@ -0,0 +1,55 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Approximate value token + * + * @author Daniel Leech + */ +class ApproximateValueToken implements TokenInterface +{ + private $value; + private $precision; + + public function __construct($value, $precision = 0) + { + $this->value = $value; + $this->precision = $precision; + } + + /** + * {@inheritdoc} + */ + public function scoreArgument($argument) + { + return round($argument, $this->precision) === round($this->value, $this->precision) ? 10 : false; + } + + /** + * {@inheritdoc} + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('≅%s', round($this->value, $this->precision)); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php new file mode 100644 index 0000000000..96b4befd7f --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php @@ -0,0 +1,86 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Array elements count token. + * + * @author Boris Mikhaylov + */ + +class ArrayCountToken implements TokenInterface +{ + private $count; + + /** + * @param integer $value + */ + public function __construct($value) + { + $this->count = $value; + } + + /** + * Scores 6 when argument has preset number of elements. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return $this->isCountable($argument) && $this->hasProperCount($argument) ? 6 : false; + } + + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('count(%s)', $this->count); + } + + /** + * Returns true if object is either array or instance of \Countable + * + * @param $argument + * @return bool + */ + private function isCountable($argument) + { + return (is_array($argument) || $argument instanceof \Countable); + } + + /** + * Returns true if $argument has expected number of elements + * + * @param array|\Countable $argument + * + * @return bool + */ + private function hasProperCount($argument) + { + return $this->count === count($argument); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php new file mode 100644 index 0000000000..0305fc7207 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php @@ -0,0 +1,143 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +use Prophecy\Exception\InvalidArgumentException; + +/** + * Array entry token. + * + * @author Boris Mikhaylov + */ +class ArrayEntryToken implements TokenInterface +{ + /** @var \Prophecy\Argument\Token\TokenInterface */ + private $key; + /** @var \Prophecy\Argument\Token\TokenInterface */ + private $value; + + /** + * @param mixed $key exact value or token + * @param mixed $value exact value or token + */ + public function __construct($key, $value) + { + $this->key = $this->wrapIntoExactValueToken($key); + $this->value = $this->wrapIntoExactValueToken($value); + } + + /** + * Scores half of combined scores from key and value tokens for same entry. Capped at 8. + * If argument implements \ArrayAccess without \Traversable, then key token is restricted to ExactValueToken. + * + * @param array|\ArrayAccess|\Traversable $argument + * + * @throws \Prophecy\Exception\InvalidArgumentException + * @return bool|int + */ + public function scoreArgument($argument) + { + if ($argument instanceof \Traversable) { + $argument = iterator_to_array($argument); + } + + if ($argument instanceof \ArrayAccess) { + $argument = $this->convertArrayAccessToEntry($argument); + } + + if (!is_array($argument) || empty($argument)) { + return false; + } + + $keyScores = array_map(array($this->key,'scoreArgument'), array_keys($argument)); + $valueScores = array_map(array($this->value,'scoreArgument'), $argument); + $scoreEntry = function ($value, $key) { + return $value && $key ? min(8, ($key + $value) / 2) : false; + }; + + return max(array_map($scoreEntry, $valueScores, $keyScores)); + } + + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('[..., %s => %s, ...]', $this->key, $this->value); + } + + /** + * Returns key + * + * @return TokenInterface + */ + public function getKey() + { + return $this->key; + } + + /** + * Returns value + * + * @return TokenInterface + */ + public function getValue() + { + return $this->value; + } + + /** + * Wraps non token $value into ExactValueToken + * + * @param $value + * @return TokenInterface + */ + private function wrapIntoExactValueToken($value) + { + return $value instanceof TokenInterface ? $value : new ExactValueToken($value); + } + + /** + * Converts instance of \ArrayAccess to key => value array entry + * + * @param \ArrayAccess $object + * + * @return array|null + * @throws \Prophecy\Exception\InvalidArgumentException + */ + private function convertArrayAccessToEntry(\ArrayAccess $object) + { + if (!$this->key instanceof ExactValueToken) { + throw new InvalidArgumentException(sprintf( + 'You can only use exact value tokens to match key of ArrayAccess object'.PHP_EOL. + 'But you used `%s`.', + $this->key + )); + } + + $key = $this->key->getValue(); + + return $object->offsetExists($key) ? array($key => $object[$key]) : array(); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php new file mode 100644 index 0000000000..5d41fa487c --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php @@ -0,0 +1,82 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Array every entry token. + * + * @author Adrien Brault + */ +class ArrayEveryEntryToken implements TokenInterface +{ + /** + * @var TokenInterface + */ + private $value; + + /** + * @param mixed $value exact value or token + */ + public function __construct($value) + { + if (!$value instanceof TokenInterface) { + $value = new ExactValueToken($value); + } + + $this->value = $value; + } + + /** + * {@inheritdoc} + */ + public function scoreArgument($argument) + { + if (!$argument instanceof \Traversable && !is_array($argument)) { + return false; + } + + $scores = array(); + foreach ($argument as $key => $argumentEntry) { + $scores[] = $this->value->scoreArgument($argumentEntry); + } + + if (empty($scores) || in_array(false, $scores, true)) { + return false; + } + + return array_sum($scores) / count($scores); + } + + /** + * {@inheritdoc} + */ + public function isLast() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('[%s, ..., %s]', $this->value, $this->value); + } + + /** + * @return TokenInterface + */ + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php new file mode 100644 index 0000000000..f45ba20bec --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php @@ -0,0 +1,75 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +use Prophecy\Exception\InvalidArgumentException; + +/** + * Callback-verified token. + * + * @author Konstantin Kudryashov + */ +class CallbackToken implements TokenInterface +{ + private $callback; + + /** + * Initializes token. + * + * @param callable $callback + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($callback) + { + if (!is_callable($callback)) { + throw new InvalidArgumentException(sprintf( + 'Callable expected as an argument to CallbackToken, but got %s.', + gettype($callback) + )); + } + + $this->callback = $callback; + } + + /** + * Scores 7 if callback returns true, false otherwise. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return call_user_func($this->callback, $argument) ? 7 : false; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return 'callback()'; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php new file mode 100644 index 0000000000..aa960f3fbc --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php @@ -0,0 +1,116 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +use SebastianBergmann\Comparator\ComparisonFailure; +use Prophecy\Comparator\Factory as ComparatorFactory; +use Prophecy\Util\StringUtil; + +/** + * Exact value token. + * + * @author Konstantin Kudryashov + */ +class ExactValueToken implements TokenInterface +{ + private $value; + private $string; + private $util; + private $comparatorFactory; + + /** + * Initializes token. + * + * @param mixed $value + * @param StringUtil $util + * @param ComparatorFactory $comparatorFactory + */ + public function __construct($value, StringUtil $util = null, ComparatorFactory $comparatorFactory = null) + { + $this->value = $value; + $this->util = $util ?: new StringUtil(); + + $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); + } + + /** + * Scores 10 if argument matches preset value. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + if (is_object($argument) && is_object($this->value)) { + $comparator = $this->comparatorFactory->getComparatorFor( + $argument, $this->value + ); + + try { + $comparator->assertEquals($argument, $this->value); + return 10; + } catch (ComparisonFailure $failure) {} + } + + // If either one is an object it should be castable to a string + if (is_object($argument) xor is_object($this->value)) { + if (is_object($argument) && !method_exists($argument, '__toString')) { + return false; + } + + if (is_object($this->value) && !method_exists($this->value, '__toString')) { + return false; + } + } elseif (is_numeric($argument) && is_numeric($this->value)) { + // noop + } elseif (gettype($argument) !== gettype($this->value)) { + return false; + } + + return $argument == $this->value ? 10 : false; + } + + /** + * Returns preset value against which token checks arguments. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + if (null === $this->string) { + $this->string = sprintf('exact(%s)', $this->util->stringify($this->value)); + } + + return $this->string; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php new file mode 100644 index 0000000000..0b6d23ab66 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php @@ -0,0 +1,74 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +use Prophecy\Util\StringUtil; + +/** + * Identical value token. + * + * @author Florian Voutzinos + */ +class IdenticalValueToken implements TokenInterface +{ + private $value; + private $string; + private $util; + + /** + * Initializes token. + * + * @param mixed $value + * @param StringUtil $util + */ + public function __construct($value, StringUtil $util = null) + { + $this->value = $value; + $this->util = $util ?: new StringUtil(); + } + + /** + * Scores 11 if argument matches preset value. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return $argument === $this->value ? 11 : false; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + if (null === $this->string) { + $this->string = sprintf('identical(%s)', $this->util->stringify($this->value)); + } + + return $this->string; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php new file mode 100644 index 0000000000..4ee1b25e11 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php @@ -0,0 +1,80 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Logical AND token. + * + * @author Boris Mikhaylov + */ +class LogicalAndToken implements TokenInterface +{ + private $tokens = array(); + + /** + * @param array $arguments exact values or tokens + */ + public function __construct(array $arguments) + { + foreach ($arguments as $argument) { + if (!$argument instanceof TokenInterface) { + $argument = new ExactValueToken($argument); + } + $this->tokens[] = $argument; + } + } + + /** + * Scores maximum score from scores returned by tokens for this argument if all of them score. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + if (0 === count($this->tokens)) { + return false; + } + + $maxScore = 0; + foreach ($this->tokens as $token) { + $score = $token->scoreArgument($argument); + if (false === $score) { + return false; + } + $maxScore = max($score, $maxScore); + } + + return $maxScore; + } + + /** + * Returns false. + * + * @return boolean + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('bool(%s)', implode(' AND ', $this->tokens)); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php new file mode 100644 index 0000000000..623efa57a7 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php @@ -0,0 +1,73 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Logical NOT token. + * + * @author Boris Mikhaylov + */ +class LogicalNotToken implements TokenInterface +{ + /** @var \Prophecy\Argument\Token\TokenInterface */ + private $token; + + /** + * @param mixed $value exact value or token + */ + public function __construct($value) + { + $this->token = $value instanceof TokenInterface? $value : new ExactValueToken($value); + } + + /** + * Scores 4 when preset token does not match the argument. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + return false === $this->token->scoreArgument($argument) ? 4 : false; + } + + /** + * Returns true if preset token is last. + * + * @return bool|int + */ + public function isLast() + { + return $this->token->isLast(); + } + + /** + * Returns originating token. + * + * @return TokenInterface + */ + public function getOriginatingToken() + { + return $this->token; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('not(%s)', $this->token); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php new file mode 100644 index 0000000000..d771077676 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php @@ -0,0 +1,104 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +use SebastianBergmann\Comparator\ComparisonFailure; +use Prophecy\Comparator\Factory as ComparatorFactory; +use Prophecy\Util\StringUtil; + +/** + * Object state-checker token. + * + * @author Konstantin Kudryashov + */ +class ObjectStateToken implements TokenInterface +{ + private $name; + private $value; + private $util; + private $comparatorFactory; + + /** + * Initializes token. + * + * @param string $methodName + * @param mixed $value Expected return value + * @param null|StringUtil $util + * @param ComparatorFactory $comparatorFactory + */ + public function __construct( + $methodName, + $value, + StringUtil $util = null, + ComparatorFactory $comparatorFactory = null + ) { + $this->name = $methodName; + $this->value = $value; + $this->util = $util ?: new StringUtil; + + $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); + } + + /** + * Scores 8 if argument is an object, which method returns expected value. + * + * @param mixed $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + if (is_object($argument) && method_exists($argument, $this->name)) { + $actual = call_user_func(array($argument, $this->name)); + + $comparator = $this->comparatorFactory->getComparatorFor( + $this->value, $actual + ); + + try { + $comparator->assertEquals($this->value, $actual); + return 8; + } catch (ComparisonFailure $failure) { + return false; + } + } + + if (is_object($argument) && property_exists($argument, $this->name)) { + return $argument->{$this->name} === $this->value ? 8 : false; + } + + return false; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('state(%s(), %s)', + $this->name, + $this->util->stringify($this->value) + ); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php new file mode 100644 index 0000000000..24ff8c2ecf --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php @@ -0,0 +1,67 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * String contains token. + * + * @author Peter Mitchell + */ +class StringContainsToken implements TokenInterface +{ + private $value; + + /** + * Initializes token. + * + * @param string $value + */ + public function __construct($value) + { + $this->value = $value; + } + + public function scoreArgument($argument) + { + return strpos($argument, $this->value) !== false ? 6 : false; + } + + /** + * Returns preset value against which token checks arguments. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('contains("%s")', $this->value); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php new file mode 100644 index 0000000000..625d3bad22 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php @@ -0,0 +1,43 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +/** + * Argument token interface. + * + * @author Konstantin Kudryashov + */ +interface TokenInterface +{ + /** + * Calculates token match score for provided argument. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument); + + /** + * Returns true if this token prevents check of other tokens (is last one). + * + * @return bool|int + */ + public function isLast(); + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString(); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php new file mode 100644 index 0000000000..cb65132ca0 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php @@ -0,0 +1,76 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Argument\Token; + +use Prophecy\Exception\InvalidArgumentException; + +/** + * Value type token. + * + * @author Konstantin Kudryashov + */ +class TypeToken implements TokenInterface +{ + private $type; + + /** + * @param string $type + */ + public function __construct($type) + { + $checker = "is_{$type}"; + if (!function_exists($checker) && !interface_exists($type) && !class_exists($type)) { + throw new InvalidArgumentException(sprintf( + 'Type or class name expected as an argument to TypeToken, but got %s.', $type + )); + } + + $this->type = $type; + } + + /** + * Scores 5 if argument has the same type this token was constructed with. + * + * @param $argument + * + * @return bool|int + */ + public function scoreArgument($argument) + { + $checker = "is_{$this->type}"; + if (function_exists($checker)) { + return call_user_func($checker, $argument) ? 5 : false; + } + + return $argument instanceof $this->type ? 5 : false; + } + + /** + * Returns false. + * + * @return bool + */ + public function isLast() + { + return false; + } + + /** + * Returns string representation for token. + * + * @return string + */ + public function __toString() + { + return sprintf('type(%s)', $this->type); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Call/Call.php b/vendor/phpspec/prophecy/src/Prophecy/Call/Call.php new file mode 100644 index 0000000000..2f3fbadb1a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Call/Call.php @@ -0,0 +1,127 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Call; + +use Exception; + +/** + * Call object. + * + * @author Konstantin Kudryashov + */ +class Call +{ + private $methodName; + private $arguments; + private $returnValue; + private $exception; + private $file; + private $line; + + /** + * Initializes call. + * + * @param string $methodName + * @param array $arguments + * @param mixed $returnValue + * @param Exception $exception + * @param null|string $file + * @param null|int $line + */ + public function __construct($methodName, array $arguments, $returnValue, + Exception $exception = null, $file, $line) + { + $this->methodName = $methodName; + $this->arguments = $arguments; + $this->returnValue = $returnValue; + $this->exception = $exception; + + if ($file) { + $this->file = $file; + $this->line = intval($line); + } + } + + /** + * Returns called method name. + * + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * Returns called method arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns called method return value. + * + * @return null|mixed + */ + public function getReturnValue() + { + return $this->returnValue; + } + + /** + * Returns exception that call thrown. + * + * @return null|Exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Returns callee filename. + * + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * Returns callee line number. + * + * @return int + */ + public function getLine() + { + return $this->line; + } + + /** + * Returns short notation for callee place. + * + * @return string + */ + public function getCallPlace() + { + if (null === $this->file) { + return 'unknown'; + } + + return sprintf('%s:%d', $this->file, $this->line); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Call/CallCenter.php b/vendor/phpspec/prophecy/src/Prophecy/Call/CallCenter.php new file mode 100644 index 0000000000..53b80f058b --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Call/CallCenter.php @@ -0,0 +1,171 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Call; + +use Prophecy\Exception\Prophecy\MethodProphecyException; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Call\UnexpectedCallException; + +/** + * Calls receiver & manager. + * + * @author Konstantin Kudryashov + */ +class CallCenter +{ + private $util; + + /** + * @var Call[] + */ + private $recordedCalls = array(); + + /** + * Initializes call center. + * + * @param StringUtil $util + */ + public function __construct(StringUtil $util = null) + { + $this->util = $util ?: new StringUtil; + } + + /** + * Makes and records specific method call for object prophecy. + * + * @param ObjectProphecy $prophecy + * @param string $methodName + * @param array $arguments + * + * @return mixed Returns null if no promise for prophecy found or promise return value. + * + * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found + */ + public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments) + { + // For efficiency exclude 'args' from the generated backtrace + if (PHP_VERSION_ID >= 50400) { + // Limit backtrace to last 3 calls as we don't use the rest + // Limit argument was introduced in PHP 5.4.0 + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + } elseif (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) { + // DEBUG_BACKTRACE_IGNORE_ARGS was introduced in PHP 5.3.6 + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } else { + $backtrace = debug_backtrace(); + } + + $file = $line = null; + if (isset($backtrace[2]) && isset($backtrace[2]['file'])) { + $file = $backtrace[2]['file']; + $line = $backtrace[2]['line']; + } + + // If no method prophecies defined, then it's a dummy, so we'll just return null + if ('__destruct' === $methodName || 0 == count($prophecy->getMethodProphecies())) { + $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line); + + return null; + } + + // There are method prophecies, so it's a fake/stub. Searching prophecy for this call + $matches = array(); + foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) { + if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) { + $matches[] = array($score, $methodProphecy); + } + } + + // If fake/stub doesn't have method prophecy for this call - throw exception + if (!count($matches)) { + throw $this->createUnexpectedCallException($prophecy, $methodName, $arguments); + } + + // Sort matches by their score value + @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; }); + + // If Highest rated method prophecy has a promise - execute it or return null instead + $methodProphecy = $matches[0][1]; + $returnValue = null; + $exception = null; + if ($promise = $methodProphecy->getPromise()) { + try { + $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy); + } catch (\Exception $e) { + $exception = $e; + } + } + + if ($methodProphecy->hasReturnVoid() && $returnValue !== null) { + throw new MethodProphecyException( + "The method \"$methodName\" has a void return type, but the promise returned a value", + $methodProphecy + ); + } + + $this->recordedCalls[] = new Call( + $methodName, $arguments, $returnValue, $exception, $file, $line + ); + + if (null !== $exception) { + throw $exception; + } + + return $returnValue; + } + + /** + * Searches for calls by method name & arguments wildcard. + * + * @param string $methodName + * @param ArgumentsWildcard $wildcard + * + * @return Call[] + */ + public function findCalls($methodName, ArgumentsWildcard $wildcard) + { + return array_values( + array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) { + return $methodName === $call->getMethodName() + && 0 < $wildcard->scoreArguments($call->getArguments()) + ; + }) + ); + } + + private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName, + array $arguments) + { + $classname = get_class($prophecy->reveal()); + $argstring = implode(', ', array_map(array($this->util, 'stringify'), $arguments)); + $expected = implode("\n", array_map(function (MethodProphecy $methodProphecy) { + return sprintf(' - %s(%s)', + $methodProphecy->getMethodName(), + $methodProphecy->getArgumentsWildcard() + ); + }, call_user_func_array('array_merge', $prophecy->getMethodProphecies()))); + + return new UnexpectedCallException( + sprintf( + "Method call:\n". + " - %s(%s)\n". + "on %s was not expected, expected calls were:\n%s", + + $methodName, $argstring, $classname, $expected + ), + $prophecy, $methodName, $arguments + ); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php b/vendor/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php new file mode 100644 index 0000000000..874e474cc3 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php @@ -0,0 +1,42 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Comparator; + +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Comparator\ComparisonFailure; + +/** + * Closure comparator. + * + * @author Konstantin Kudryashov + */ +final class ClosureComparator extends Comparator +{ + public function accepts($expected, $actual) + { + return is_object($expected) && $expected instanceof \Closure + && is_object($actual) && $actual instanceof \Closure; + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + throw new ComparisonFailure( + $expected, + $actual, + // we don't need a diff + '', + '', + false, + 'all closures are born different' + ); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Comparator/Factory.php b/vendor/phpspec/prophecy/src/Prophecy/Comparator/Factory.php new file mode 100644 index 0000000000..2070db142b --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Comparator/Factory.php @@ -0,0 +1,47 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Comparator; + +use SebastianBergmann\Comparator\Factory as BaseFactory; + +/** + * Prophecy comparator factory. + * + * @author Konstantin Kudryashov + */ +final class Factory extends BaseFactory +{ + /** + * @var Factory + */ + private static $instance; + + public function __construct() + { + parent::__construct(); + + $this->register(new ClosureComparator()); + $this->register(new ProphecyComparator()); + } + + /** + * @return Factory + */ + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new Factory; + } + + return self::$instance; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php b/vendor/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php new file mode 100644 index 0000000000..298a8e3568 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php @@ -0,0 +1,28 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Comparator; + +use Prophecy\Prophecy\ProphecyInterface; +use SebastianBergmann\Comparator\ObjectComparator; + +class ProphecyComparator extends ObjectComparator +{ + public function accepts($expected, $actual) + { + return is_object($expected) && is_object($actual) && $actual instanceof ProphecyInterface; + } + + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array()) + { + parent::assertEquals($expected, $actual->reveal(), $delta, $canonicalize, $ignoreCase, $processed); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php new file mode 100644 index 0000000000..d6b6b1a9e0 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php @@ -0,0 +1,68 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use ReflectionClass; + +/** + * Cached class doubler. + * Prevents mirroring/creation of the same structure twice. + * + * @author Konstantin Kudryashov + */ +class CachedDoubler extends Doubler +{ + private $classes = array(); + + /** + * {@inheritdoc} + */ + public function registerClassPatch(ClassPatch\ClassPatchInterface $patch) + { + $this->classes[] = array(); + + parent::registerClassPatch($patch); + } + + /** + * {@inheritdoc} + */ + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + { + $classId = $this->generateClassId($class, $interfaces); + if (isset($this->classes[$classId])) { + return $this->classes[$classId]; + } + + return $this->classes[$classId] = parent::createDoubleClass($class, $interfaces); + } + + /** + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + private function generateClassId(ReflectionClass $class = null, array $interfaces) + { + $parts = array(); + if (null !== $class) { + $parts[] = $class->getName(); + } + foreach ($interfaces as $interface) { + $parts[] = $interface->getName(); + } + sort($parts); + + return md5(implode('', $parts)); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php new file mode 100644 index 0000000000..d6d196850c --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php @@ -0,0 +1,48 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * Class patch interface. + * Class patches extend doubles functionality or help + * Prophecy to avoid some internal PHP bugs. + * + * @author Konstantin Kudryashov + */ +interface ClassPatchInterface +{ + /** + * Checks if patch supports specific class node. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node); + + /** + * Applies patch to the specific class node. + * + * @param ClassNode $node + * @return void + */ + public function apply(ClassNode $node); + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority(); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php new file mode 100644 index 0000000000..61998fc462 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php @@ -0,0 +1,72 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; + +/** + * Disable constructor. + * Makes all constructor arguments optional. + * + * @author Konstantin Kudryashov + */ +class DisableConstructorPatch implements ClassPatchInterface +{ + /** + * Checks if class has `__construct` method. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Makes all class constructor arguments optional. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + if (!$node->hasMethod('__construct')) { + $node->addMethod(new MethodNode('__construct', '')); + + return; + } + + $constructor = $node->getMethod('__construct'); + foreach ($constructor->getArguments() as $argument) { + $argument->setDefault(null); + } + + $constructor->setCode(<< + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * Exception patch for HHVM to remove the stubs from special methods + * + * @author Christophe Coevoet + */ +class HhvmExceptionPatch implements ClassPatchInterface +{ + /** + * Supports exceptions on HHVM. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + if (!defined('HHVM_VERSION')) { + return false; + } + + return 'Exception' === $node->getParentClass() || is_subclass_of($node->getParentClass(), 'Exception'); + } + + /** + * Removes special exception static methods from the doubled methods. + * + * @param ClassNode $node + * + * @return void + */ + public function apply(ClassNode $node) + { + if ($node->hasMethod('setTraceOptions')) { + $node->getMethod('setTraceOptions')->useParentCode(); + } + if ($node->hasMethod('getTraceOptions')) { + $node->getMethod('getTraceOptions')->useParentCode(); + } + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return -50; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php new file mode 100644 index 0000000000..41ea2fc1c1 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php @@ -0,0 +1,140 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * Remove method functionality from the double which will clash with php keywords. + * + * @author Milan Magudia + */ +class KeywordPatch implements ClassPatchInterface +{ + /** + * Support any class + * + * @param ClassNode $node + * + * @return boolean + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Remove methods that clash with php keywords + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $methodNames = array_keys($node->getMethods()); + $methodsToRemove = array_intersect($methodNames, $this->getKeywords()); + foreach ($methodsToRemove as $methodName) { + $node->removeMethod($methodName); + } + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 49; + } + + /** + * Returns array of php keywords. + * + * @return array + */ + private function getKeywords() + { + if (\PHP_VERSION_ID >= 70000) { + return array('__halt_compiler'); + } + + return array( + '__halt_compiler', + 'abstract', + 'and', + 'array', + 'as', + 'break', + 'callable', + 'case', + 'catch', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'eval', + 'exit', + 'extends', + 'final', + 'finally', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'insteadof', + 'interface', + 'isset', + 'list', + 'namespace', + 'new', + 'or', + 'print', + 'private', + 'protected', + 'public', + 'require', + 'require_once', + 'return', + 'static', + 'switch', + 'throw', + 'trait', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + 'yield', + ); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php new file mode 100644 index 0000000000..5f2c607719 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php @@ -0,0 +1,89 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever; +use Prophecy\PhpDocumentor\MethodTagRetrieverInterface; + +/** + * Discover Magical API using "@method" PHPDoc format. + * + * @author Thomas Tourlourat + * @author Kévin Dunglas + * @author Théo FIDRY + */ +class MagicCallPatch implements ClassPatchInterface +{ + private $tagRetriever; + + public function __construct(MethodTagRetrieverInterface $tagRetriever = null) + { + $this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever; + } + + /** + * Support any class + * + * @param ClassNode $node + * + * @return boolean + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Discover Magical API + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $types = array_filter($node->getInterfaces(), function ($interface) { + return 0 !== strpos($interface, 'Prophecy\\'); + }); + $types[] = $node->getParentClass(); + + foreach ($types as $type) { + $reflectionClass = new \ReflectionClass($type); + $tagList = $this->tagRetriever->getTagList($reflectionClass); + + foreach($tagList as $tag) { + $methodName = $tag->getMethodName(); + + if (empty($methodName)) { + continue; + } + + if (!$reflectionClass->hasMethod($methodName)) { + $methodNode = new MethodNode($methodName); + $methodNode->setStatic($tag->isStatic()); + $node->addMethod($methodNode); + } + } + } + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return integer Priority number (higher - earlier) + */ + public function getPriority() + { + return 50; + } +} + diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php new file mode 100644 index 0000000000..fc2cc4de48 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php @@ -0,0 +1,104 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\Doubler\Generator\Node\ArgumentNode; + +/** + * Add Prophecy functionality to the double. + * This is a core class patch for Prophecy. + * + * @author Konstantin Kudryashov + */ +class ProphecySubjectPatch implements ClassPatchInterface +{ + /** + * Always returns true. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Apply Prophecy functionality to class node. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface'); + $node->addProperty('objectProphecy', 'private'); + + foreach ($node->getMethods() as $name => $method) { + if ('__construct' === strtolower($name)) { + continue; + } + + if ($method->getReturnType() === 'void') { + $method->setCode( + '$this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());' + ); + } else { + $method->setCode( + 'return $this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());' + ); + } + } + + $prophecySetter = new MethodNode('setProphecy'); + $prophecyArgument = new ArgumentNode('prophecy'); + $prophecyArgument->setTypeHint('Prophecy\Prophecy\ProphecyInterface'); + $prophecySetter->addArgument($prophecyArgument); + $prophecySetter->setCode('$this->objectProphecy = $prophecy;'); + + $prophecyGetter = new MethodNode('getProphecy'); + $prophecyGetter->setCode('return $this->objectProphecy;'); + + if ($node->hasMethod('__call')) { + $__call = $node->getMethod('__call'); + } else { + $__call = new MethodNode('__call'); + $__call->addArgument(new ArgumentNode('name')); + $__call->addArgument(new ArgumentNode('arguments')); + + $node->addMethod($__call); + } + + $__call->setCode(<<getProphecy(), func_get_arg(0) +); +PHP + ); + + $node->addMethod($prophecySetter); + $node->addMethod($prophecyGetter); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 0; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php new file mode 100644 index 0000000000..9166aeefac --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php @@ -0,0 +1,57 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * ReflectionClass::newInstance patch. + * Makes first argument of newInstance optional, since it works but signature is misleading + * + * @author Florian Klein + */ +class ReflectionClassNewInstancePatch implements ClassPatchInterface +{ + /** + * Supports ReflectionClass + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + return 'ReflectionClass' === $node->getParentClass(); + } + + /** + * Updates newInstance's first argument to make it optional + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + foreach ($node->getMethod('newInstance')->getArguments() as $argument) { + $argument->setDefault(null); + } + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher = earlier) + */ + public function getPriority() + { + return 50; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php new file mode 100644 index 0000000000..ceee94a2ef --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php @@ -0,0 +1,123 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; + +/** + * SplFileInfo patch. + * Makes SplFileInfo and derivative classes usable with Prophecy. + * + * @author Konstantin Kudryashov + */ +class SplFileInfoPatch implements ClassPatchInterface +{ + /** + * Supports everything that extends SplFileInfo. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + if (null === $node->getParentClass()) { + return false; + } + return 'SplFileInfo' === $node->getParentClass() + || is_subclass_of($node->getParentClass(), 'SplFileInfo') + ; + } + + /** + * Updated constructor code to call parent one with dummy file argument. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + if ($node->hasMethod('__construct')) { + $constructor = $node->getMethod('__construct'); + } else { + $constructor = new MethodNode('__construct'); + $node->addMethod($constructor); + } + + if ($this->nodeIsDirectoryIterator($node)) { + $constructor->setCode('return parent::__construct("' . __DIR__ . '");'); + + return; + } + + if ($this->nodeIsSplFileObject($node)) { + $filePath = str_replace('\\','\\\\',__FILE__); + $constructor->setCode('return parent::__construct("' . $filePath .'");'); + + return; + } + + if ($this->nodeIsSymfonySplFileInfo($node)) { + $filePath = str_replace('\\','\\\\',__FILE__); + $constructor->setCode('return parent::__construct("' . $filePath .'", "", "");'); + + return; + } + + $constructor->useParentCode(); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 50; + } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsDirectoryIterator(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'DirectoryIterator' === $parent + || is_subclass_of($parent, 'DirectoryIterator'); + } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsSplFileObject(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'SplFileObject' === $parent + || is_subclass_of($parent, 'SplFileObject'); + } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsSymfonySplFileInfo(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php new file mode 100644 index 0000000000..eea0202825 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php @@ -0,0 +1,83 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; + +/** + * Traversable interface patch. + * Forces classes that implement interfaces, that extend Traversable to also implement Iterator. + * + * @author Konstantin Kudryashov + */ +class TraversablePatch implements ClassPatchInterface +{ + /** + * Supports nodetree, that implement Traversable, but not Iterator or IteratorAggregate. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + if (in_array('Iterator', $node->getInterfaces())) { + return false; + } + if (in_array('IteratorAggregate', $node->getInterfaces())) { + return false; + } + + foreach ($node->getInterfaces() as $interface) { + if ('Traversable' !== $interface && !is_subclass_of($interface, 'Traversable')) { + continue; + } + if ('Iterator' === $interface || is_subclass_of($interface, 'Iterator')) { + continue; + } + if ('IteratorAggregate' === $interface || is_subclass_of($interface, 'IteratorAggregate')) { + continue; + } + + return true; + } + + return false; + } + + /** + * Forces class to implement Iterator interface. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $node->addInterface('Iterator'); + + $node->addMethod(new MethodNode('current')); + $node->addMethod(new MethodNode('key')); + $node->addMethod(new MethodNode('next')); + $node->addMethod(new MethodNode('rewind')); + $node->addMethod(new MethodNode('valid')); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 100; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php new file mode 100644 index 0000000000..699be3a2ad --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php @@ -0,0 +1,22 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +/** + * Core double interface. + * All doubled classes will implement this one. + * + * @author Konstantin Kudryashov + */ +interface DoubleInterface +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php new file mode 100644 index 0000000000..a378ae2790 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php @@ -0,0 +1,146 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use Doctrine\Instantiator\Instantiator; +use Prophecy\Doubler\ClassPatch\ClassPatchInterface; +use Prophecy\Doubler\Generator\ClassMirror; +use Prophecy\Doubler\Generator\ClassCreator; +use Prophecy\Exception\InvalidArgumentException; +use ReflectionClass; + +/** + * Cached class doubler. + * Prevents mirroring/creation of the same structure twice. + * + * @author Konstantin Kudryashov + */ +class Doubler +{ + private $mirror; + private $creator; + private $namer; + + /** + * @var ClassPatchInterface[] + */ + private $patches = array(); + + /** + * @var \Doctrine\Instantiator\Instantiator + */ + private $instantiator; + + /** + * Initializes doubler. + * + * @param ClassMirror $mirror + * @param ClassCreator $creator + * @param NameGenerator $namer + */ + public function __construct(ClassMirror $mirror = null, ClassCreator $creator = null, + NameGenerator $namer = null) + { + $this->mirror = $mirror ?: new ClassMirror; + $this->creator = $creator ?: new ClassCreator; + $this->namer = $namer ?: new NameGenerator; + } + + /** + * Returns list of registered class patches. + * + * @return ClassPatchInterface[] + */ + public function getClassPatches() + { + return $this->patches; + } + + /** + * Registers new class patch. + * + * @param ClassPatchInterface $patch + */ + public function registerClassPatch(ClassPatchInterface $patch) + { + $this->patches[] = $patch; + + @usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) { + return $patch2->getPriority() - $patch1->getPriority(); + }); + } + + /** + * Creates double from specific class or/and list of interfaces. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces Array of ReflectionClass instances + * @param array $args Constructor arguments + * + * @return DoubleInterface + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function double(ReflectionClass $class = null, array $interfaces, array $args = null) + { + foreach ($interfaces as $interface) { + if (!$interface instanceof ReflectionClass) { + throw new InvalidArgumentException(sprintf( + "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". + "a second argument to `Doubler::double(...)`, but got %s.", + is_object($interface) ? get_class($interface).' class' : gettype($interface) + )); + } + } + + $classname = $this->createDoubleClass($class, $interfaces); + $reflection = new ReflectionClass($classname); + + if (null !== $args) { + return $reflection->newInstanceArgs($args); + } + if ((null === $constructor = $reflection->getConstructor()) + || ($constructor->isPublic() && !$constructor->isFinal())) { + return $reflection->newInstance(); + } + + if (!$this->instantiator) { + $this->instantiator = new Instantiator(); + } + + return $this->instantiator->instantiate($classname); + } + + /** + * Creates double class and returns its FQN. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + { + $name = $this->namer->name($class, $interfaces); + $node = $this->mirror->reflect($class, $interfaces); + + foreach ($this->patches as $patch) { + if ($patch->supports($node)) { + $patch->apply($node); + } + } + + $this->creator->create($name, $node); + + return $name; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php new file mode 100644 index 0000000000..891faa8fbe --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php @@ -0,0 +1,129 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +/** + * Class code creator. + * Generates PHP code for specific class node tree. + * + * @author Konstantin Kudryashov + */ +class ClassCodeGenerator +{ + /** + * @var TypeHintReference + */ + private $typeHintReference; + + public function __construct(TypeHintReference $typeHintReference = null) + { + $this->typeHintReference = $typeHintReference ?: new TypeHintReference(); + } + + /** + * Generates PHP code for class node. + * + * @param string $classname + * @param Node\ClassNode $class + * + * @return string + */ + public function generate($classname, Node\ClassNode $class) + { + $parts = explode('\\', $classname); + $classname = array_pop($parts); + $namespace = implode('\\', $parts); + + $code = sprintf("class %s extends \%s implements %s {\n", + $classname, $class->getParentClass(), implode(', ', + array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces()) + ) + ); + + foreach ($class->getProperties() as $name => $visibility) { + $code .= sprintf("%s \$%s;\n", $visibility, $name); + } + $code .= "\n"; + + foreach ($class->getMethods() as $method) { + $code .= $this->generateMethod($method)."\n"; + } + $code .= "\n}"; + + return sprintf("namespace %s {\n%s\n}", $namespace, $code); + } + + private function generateMethod(Node\MethodNode $method) + { + $php = sprintf("%s %s function %s%s(%s)%s {\n", + $method->getVisibility(), + $method->isStatic() ? 'static' : '', + $method->returnsReference() ? '&':'', + $method->getName(), + implode(', ', $this->generateArguments($method->getArguments())), + $this->getReturnType($method) + ); + $php .= $method->getCode()."\n"; + + return $php.'}'; + } + + /** + * @return string + */ + private function getReturnType(Node\MethodNode $method) + { + if (version_compare(PHP_VERSION, '7.1', '>=')) { + if ($method->hasReturnType()) { + return $method->hasNullableReturnType() + ? sprintf(': ?%s', $method->getReturnType()) + : sprintf(': %s', $method->getReturnType()); + } + } + + if (version_compare(PHP_VERSION, '7.0', '>=')) { + return $method->hasReturnType() && $method->getReturnType() !== 'void' + ? sprintf(': %s', $method->getReturnType()) + : ''; + } + + return ''; + } + + private function generateArguments(array $arguments) + { + $typeHintReference = $this->typeHintReference; + return array_map(function (Node\ArgumentNode $argument) use ($typeHintReference) { + $php = ''; + + if (version_compare(PHP_VERSION, '7.1', '>=')) { + $php .= $argument->isNullable() ? '?' : ''; + } + + if ($hint = $argument->getTypeHint()) { + $php .= $typeHintReference->isBuiltInParamTypeHint($hint) ? $hint : '\\'.$hint; + } + + $php .= ' '.($argument->isPassedByReference() ? '&' : ''); + + $php .= $argument->isVariadic() ? '...' : ''; + + $php .= '$'.$argument->getName(); + + if ($argument->isOptional() && !$argument->isVariadic()) { + $php .= ' = '.var_export($argument->getDefault(), true); + } + + return $php; + }, $arguments); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php new file mode 100644 index 0000000000..882a4a4b7f --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php @@ -0,0 +1,67 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +use Prophecy\Exception\Doubler\ClassCreatorException; + +/** + * Class creator. + * Creates specific class in current environment. + * + * @author Konstantin Kudryashov + */ +class ClassCreator +{ + private $generator; + + /** + * Initializes creator. + * + * @param ClassCodeGenerator $generator + */ + public function __construct(ClassCodeGenerator $generator = null) + { + $this->generator = $generator ?: new ClassCodeGenerator; + } + + /** + * Creates class. + * + * @param string $classname + * @param Node\ClassNode $class + * + * @return mixed + * + * @throws \Prophecy\Exception\Doubler\ClassCreatorException + */ + public function create($classname, Node\ClassNode $class) + { + $code = $this->generator->generate($classname, $class); + $return = eval($code); + + if (!class_exists($classname, false)) { + if (count($class->getInterfaces())) { + throw new ClassCreatorException(sprintf( + 'Could not double `%s` and implement interfaces: [%s].', + $class->getParentClass(), implode(', ', $class->getInterfaces()) + ), $class); + } + + throw new ClassCreatorException( + sprintf('Could not double `%s`.', $class->getParentClass()), + $class + ); + } + + return $return; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php new file mode 100644 index 0000000000..9f99239f69 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php @@ -0,0 +1,258 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Exception\Doubler\ClassMirrorException; +use ReflectionClass; +use ReflectionMethod; +use ReflectionParameter; + +/** + * Class mirror. + * Core doubler class. Mirrors specific class and/or interfaces into class node tree. + * + * @author Konstantin Kudryashov + */ +class ClassMirror +{ + private static $reflectableMethods = array( + '__construct', + '__destruct', + '__sleep', + '__wakeup', + '__toString', + '__call', + '__invoke' + ); + + /** + * Reflects provided arguments into class node. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return Node\ClassNode + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function reflect(ReflectionClass $class = null, array $interfaces) + { + $node = new Node\ClassNode; + + if (null !== $class) { + if (true === $class->isInterface()) { + throw new InvalidArgumentException(sprintf( + "Could not reflect %s as a class, because it\n". + "is interface - use the second argument instead.", + $class->getName() + )); + } + + $this->reflectClassToNode($class, $node); + } + + foreach ($interfaces as $interface) { + if (!$interface instanceof ReflectionClass) { + throw new InvalidArgumentException(sprintf( + "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". + "a second argument to `ClassMirror::reflect(...)`, but got %s.", + is_object($interface) ? get_class($interface).' class' : gettype($interface) + )); + } + if (false === $interface->isInterface()) { + throw new InvalidArgumentException(sprintf( + "Could not reflect %s as an interface, because it\n". + "is class - use the first argument instead.", + $interface->getName() + )); + } + + $this->reflectInterfaceToNode($interface, $node); + } + + $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface'); + + return $node; + } + + private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node) + { + if (true === $class->isFinal()) { + throw new ClassMirrorException(sprintf( + 'Could not reflect class %s as it is marked final.', $class->getName() + ), $class); + } + + $node->setParentClass($class->getName()); + + foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) { + if (false === $method->isProtected()) { + continue; + } + + $this->reflectMethodToNode($method, $node); + } + + foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (0 === strpos($method->getName(), '_') + && !in_array($method->getName(), self::$reflectableMethods)) { + continue; + } + + if (true === $method->isFinal()) { + $node->addUnextendableMethod($method->getName()); + continue; + } + + $this->reflectMethodToNode($method, $node); + } + } + + private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node) + { + $node->addInterface($interface->getName()); + + foreach ($interface->getMethods() as $method) { + $this->reflectMethodToNode($method, $node); + } + } + + private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode) + { + $node = new Node\MethodNode($method->getName()); + + if (true === $method->isProtected()) { + $node->setVisibility('protected'); + } + + if (true === $method->isStatic()) { + $node->setStatic(); + } + + if (true === $method->returnsReference()) { + $node->setReturnsReference(); + } + + if (version_compare(PHP_VERSION, '7.0', '>=') && $method->hasReturnType()) { + $returnType = (string) $method->getReturnType(); + $returnTypeLower = strtolower($returnType); + + if ('self' === $returnTypeLower) { + $returnType = $method->getDeclaringClass()->getName(); + } + if ('parent' === $returnTypeLower) { + $returnType = $method->getDeclaringClass()->getParentClass()->getName(); + } + + $node->setReturnType($returnType); + + if (version_compare(PHP_VERSION, '7.1', '>=') && $method->getReturnType()->allowsNull()) { + $node->setNullableReturnType(true); + } + } + + if (is_array($params = $method->getParameters()) && count($params)) { + foreach ($params as $param) { + $this->reflectArgumentToNode($param, $node); + } + } + + $classNode->addMethod($node); + } + + private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode) + { + $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName(); + $node = new Node\ArgumentNode($name); + + $node->setTypeHint($this->getTypeHint($parameter)); + + if ($this->isVariadic($parameter)) { + $node->setAsVariadic(); + } + + if ($this->hasDefaultValue($parameter)) { + $node->setDefault($this->getDefaultValue($parameter)); + } + + if ($parameter->isPassedByReference()) { + $node->setAsPassedByReference(); + } + + $methodNode->addArgument($node); + } + + private function hasDefaultValue(ReflectionParameter $parameter) + { + if ($this->isVariadic($parameter)) { + return false; + } + + if ($parameter->isDefaultValueAvailable()) { + return true; + } + + return $parameter->isOptional() || $this->isNullable($parameter); + } + + private function getDefaultValue(ReflectionParameter $parameter) + { + if (!$parameter->isDefaultValueAvailable()) { + return null; + } + + return $parameter->getDefaultValue(); + } + + private function getTypeHint(ReflectionParameter $parameter) + { + if (null !== $className = $this->getParameterClassName($parameter)) { + return $className; + } + + if (true === $parameter->isArray()) { + return 'array'; + } + + if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) { + return 'callable'; + } + + if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) { + return (string) $parameter->getType(); + } + + return null; + } + + private function isVariadic(ReflectionParameter $parameter) + { + return PHP_VERSION_ID >= 50600 && $parameter->isVariadic(); + } + + private function isNullable(ReflectionParameter $parameter) + { + return $parameter->allowsNull() && null !== $this->getTypeHint($parameter); + } + + private function getParameterClassName(ReflectionParameter $parameter) + { + try { + return $parameter->getClass() ? $parameter->getClass()->getName() : null; + } catch (\ReflectionException $e) { + preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches); + + return isset($matches[1]) ? $matches[1] : null; + } + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php new file mode 100644 index 0000000000..dd29b68fca --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php @@ -0,0 +1,102 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator\Node; + +/** + * Argument node. + * + * @author Konstantin Kudryashov + */ +class ArgumentNode +{ + private $name; + private $typeHint; + private $default; + private $optional = false; + private $byReference = false; + private $isVariadic = false; + private $isNullable = false; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function getTypeHint() + { + return $this->typeHint; + } + + public function setTypeHint($typeHint = null) + { + $this->typeHint = $typeHint; + } + + public function hasDefault() + { + return $this->isOptional() && !$this->isVariadic(); + } + + public function getDefault() + { + return $this->default; + } + + public function setDefault($default = null) + { + $this->optional = true; + $this->default = $default; + } + + public function isOptional() + { + return $this->optional; + } + + public function setAsPassedByReference($byReference = true) + { + $this->byReference = $byReference; + } + + public function isPassedByReference() + { + return $this->byReference; + } + + public function setAsVariadic($isVariadic = true) + { + $this->isVariadic = $isVariadic; + } + + public function isVariadic() + { + return $this->isVariadic; + } + + public function isNullable() + { + return $this->isNullable; + } + + public function setAsNullable($isNullable = true) + { + $this->isNullable = $isNullable; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php new file mode 100644 index 0000000000..1499a1d325 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php @@ -0,0 +1,166 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Exception\Doubler\MethodNotExtendableException; +use Prophecy\Exception\InvalidArgumentException; + +/** + * Class node. + * + * @author Konstantin Kudryashov + */ +class ClassNode +{ + private $parentClass = 'stdClass'; + private $interfaces = array(); + private $properties = array(); + private $unextendableMethods = array(); + + /** + * @var MethodNode[] + */ + private $methods = array(); + + public function getParentClass() + { + return $this->parentClass; + } + + /** + * @param string $class + */ + public function setParentClass($class) + { + $this->parentClass = $class ?: 'stdClass'; + } + + /** + * @return string[] + */ + public function getInterfaces() + { + return $this->interfaces; + } + + /** + * @param string $interface + */ + public function addInterface($interface) + { + if ($this->hasInterface($interface)) { + return; + } + + array_unshift($this->interfaces, $interface); + } + + /** + * @param string $interface + * + * @return bool + */ + public function hasInterface($interface) + { + return in_array($interface, $this->interfaces); + } + + public function getProperties() + { + return $this->properties; + } + + public function addProperty($name, $visibility = 'public') + { + $visibility = strtolower($visibility); + + if (!in_array($visibility, array('public', 'private', 'protected'))) { + throw new InvalidArgumentException(sprintf( + '`%s` property visibility is not supported.', $visibility + )); + } + + $this->properties[$name] = $visibility; + } + + /** + * @return MethodNode[] + */ + public function getMethods() + { + return $this->methods; + } + + public function addMethod(MethodNode $method) + { + if (!$this->isExtendable($method->getName())){ + $message = sprintf( + 'Method `%s` is not extendable, so can not be added.', $method->getName() + ); + throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName()); + } + $this->methods[$method->getName()] = $method; + } + + public function removeMethod($name) + { + unset($this->methods[$name]); + } + + /** + * @param string $name + * + * @return MethodNode|null + */ + public function getMethod($name) + { + return $this->hasMethod($name) ? $this->methods[$name] : null; + } + + /** + * @param string $name + * + * @return bool + */ + public function hasMethod($name) + { + return isset($this->methods[$name]); + } + + /** + * @return string[] + */ + public function getUnextendableMethods() + { + return $this->unextendableMethods; + } + + /** + * @param string $unextendableMethod + */ + public function addUnextendableMethod($unextendableMethod) + { + if (!$this->isExtendable($unextendableMethod)){ + return; + } + $this->unextendableMethods[] = $unextendableMethod; + } + + /** + * @param string $method + * @return bool + */ + public function isExtendable($method) + { + return !in_array($method, $this->unextendableMethods); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php new file mode 100644 index 0000000000..c74b48314d --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php @@ -0,0 +1,198 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Doubler\Generator\TypeHintReference; +use Prophecy\Exception\InvalidArgumentException; + +/** + * Method node. + * + * @author Konstantin Kudryashov + */ +class MethodNode +{ + private $name; + private $code; + private $visibility = 'public'; + private $static = false; + private $returnsReference = false; + private $returnType; + private $nullableReturnType = false; + + /** + * @var ArgumentNode[] + */ + private $arguments = array(); + + /** + * @var TypeHintReference + */ + private $typeHintReference; + + /** + * @param string $name + * @param string $code + */ + public function __construct($name, $code = null, TypeHintReference $typeHintReference = null) + { + $this->name = $name; + $this->code = $code; + $this->typeHintReference = $typeHintReference ?: new TypeHintReference(); + } + + public function getVisibility() + { + return $this->visibility; + } + + /** + * @param string $visibility + */ + public function setVisibility($visibility) + { + $visibility = strtolower($visibility); + + if (!in_array($visibility, array('public', 'private', 'protected'))) { + throw new InvalidArgumentException(sprintf( + '`%s` method visibility is not supported.', $visibility + )); + } + + $this->visibility = $visibility; + } + + public function isStatic() + { + return $this->static; + } + + public function setStatic($static = true) + { + $this->static = (bool) $static; + } + + public function returnsReference() + { + return $this->returnsReference; + } + + public function setReturnsReference() + { + $this->returnsReference = true; + } + + public function getName() + { + return $this->name; + } + + public function addArgument(ArgumentNode $argument) + { + $this->arguments[] = $argument; + } + + /** + * @return ArgumentNode[] + */ + public function getArguments() + { + return $this->arguments; + } + + public function hasReturnType() + { + return null !== $this->returnType; + } + + /** + * @param string $type + */ + public function setReturnType($type = null) + { + if ($type === '' || $type === null) { + $this->returnType = null; + return; + } + $typeMap = array( + 'double' => 'float', + 'real' => 'float', + 'boolean' => 'bool', + 'integer' => 'int', + ); + if (isset($typeMap[$type])) { + $type = $typeMap[$type]; + } + $this->returnType = $this->typeHintReference->isBuiltInReturnTypeHint($type) ? + $type : + '\\' . ltrim($type, '\\'); + } + + public function getReturnType() + { + return $this->returnType; + } + + /** + * @param bool $bool + */ + public function setNullableReturnType($bool = true) + { + $this->nullableReturnType = (bool) $bool; + } + + /** + * @return bool + */ + public function hasNullableReturnType() + { + return $this->nullableReturnType; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + public function getCode() + { + if ($this->returnsReference) + { + return "throw new \Prophecy\Exception\Doubler\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), '{$this->name}');"; + } + + return (string) $this->code; + } + + public function useParentCode() + { + $this->code = sprintf( + 'return parent::%s(%s);', $this->getName(), implode(', ', + array_map(array($this, 'generateArgument'), $this->arguments) + ) + ); + } + + private function generateArgument(ArgumentNode $arg) + { + $argument = '$'.$arg->getName(); + + if ($arg->isVariadic()) { + $argument = '...'.$argument; + } + + return $argument; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php new file mode 100644 index 0000000000..d720b15159 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php @@ -0,0 +1,22 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +/** + * Reflection interface. + * All reflected classes implement this interface. + * + * @author Konstantin Kudryashov + */ +interface ReflectionInterface +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php new file mode 100644 index 0000000000..ce952029b0 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php @@ -0,0 +1,46 @@ += 50400; + + case 'bool': + case 'float': + case 'int': + case 'string': + return PHP_VERSION_ID >= 70000; + + case 'iterable': + return PHP_VERSION_ID >= 70100; + + case 'object': + return PHP_VERSION_ID >= 70200; + + default: + return false; + } + } + + public function isBuiltInReturnTypeHint($type) + { + if ($type === 'void') { + return PHP_VERSION_ID >= 70100; + } + + return $this->isBuiltInParamTypeHint($type); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php new file mode 100644 index 0000000000..8a99c4ce86 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php @@ -0,0 +1,127 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use Prophecy\Exception\Doubler\DoubleException; +use Prophecy\Exception\Doubler\ClassNotFoundException; +use Prophecy\Exception\Doubler\InterfaceNotFoundException; +use ReflectionClass; + +/** + * Lazy double. + * Gives simple interface to describe double before creating it. + * + * @author Konstantin Kudryashov + */ +class LazyDouble +{ + private $doubler; + private $class; + private $interfaces = array(); + private $arguments = null; + private $double; + + /** + * Initializes lazy double. + * + * @param Doubler $doubler + */ + public function __construct(Doubler $doubler) + { + $this->doubler = $doubler; + } + + /** + * Tells doubler to use specific class as parent one for double. + * + * @param string|ReflectionClass $class + * + * @throws \Prophecy\Exception\Doubler\ClassNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException + */ + public function setParentClass($class) + { + if (null !== $this->double) { + throw new DoubleException('Can not extend class with already instantiated double.'); + } + + if (!$class instanceof ReflectionClass) { + if (!class_exists($class)) { + throw new ClassNotFoundException(sprintf('Class %s not found.', $class), $class); + } + + $class = new ReflectionClass($class); + } + + $this->class = $class; + } + + /** + * Tells doubler to implement specific interface with double. + * + * @param string|ReflectionClass $interface + * + * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException + */ + public function addInterface($interface) + { + if (null !== $this->double) { + throw new DoubleException( + 'Can not implement interface with already instantiated double.' + ); + } + + if (!$interface instanceof ReflectionClass) { + if (!interface_exists($interface)) { + throw new InterfaceNotFoundException( + sprintf('Interface %s not found.', $interface), + $interface + ); + } + + $interface = new ReflectionClass($interface); + } + + $this->interfaces[] = $interface; + } + + /** + * Sets constructor arguments. + * + * @param array $arguments + */ + public function setArguments(array $arguments = null) + { + $this->arguments = $arguments; + } + + /** + * Creates double instance or returns already created one. + * + * @return DoubleInterface + */ + public function getInstance() + { + if (null === $this->double) { + if (null !== $this->arguments) { + return $this->double = $this->doubler->double( + $this->class, $this->interfaces, $this->arguments + ); + } + + $this->double = $this->doubler->double($this->class, $this->interfaces); + } + + return $this->double; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php new file mode 100644 index 0000000000..d67ec6a4db --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php @@ -0,0 +1,52 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use ReflectionClass; + +/** + * Name generator. + * Generates classname for double. + * + * @author Konstantin Kudryashov + */ +class NameGenerator +{ + private static $counter = 1; + + /** + * Generates name. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + public function name(ReflectionClass $class = null, array $interfaces) + { + $parts = array(); + + if (null !== $class) { + $parts[] = $class->getName(); + } else { + foreach ($interfaces as $interface) { + $parts[] = $interface->getShortName(); + } + } + + if (!count($parts)) { + $parts[] = 'stdClass'; + } + + return sprintf('Double\%s\P%d', implode('\\', $parts), self::$counter++); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php new file mode 100644 index 0000000000..48ed22542d --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php @@ -0,0 +1,40 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Call; + +use Prophecy\Exception\Prophecy\ObjectProphecyException; +use Prophecy\Prophecy\ObjectProphecy; + +class UnexpectedCallException extends ObjectProphecyException +{ + private $methodName; + private $arguments; + + public function __construct($message, ObjectProphecy $objectProphecy, + $methodName, array $arguments) + { + parent::__construct($message, $objectProphecy); + + $this->methodName = $methodName; + $this->arguments = $arguments; + } + + public function getMethodName() + { + return $this->methodName; + } + + public function getArguments() + { + return $this->arguments; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php new file mode 100644 index 0000000000..822918a294 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php @@ -0,0 +1,31 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +class ClassCreatorException extends \RuntimeException implements DoublerException +{ + private $node; + + public function __construct($message, ClassNode $node) + { + parent::__construct($message); + + $this->node = $node; + } + + public function getClassNode() + { + return $this->node; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php new file mode 100644 index 0000000000..8fc53b8b52 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php @@ -0,0 +1,31 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +use ReflectionClass; + +class ClassMirrorException extends \RuntimeException implements DoublerException +{ + private $class; + + public function __construct($message, ReflectionClass $class) + { + parent::__construct($message); + + $this->class = $class; + } + + public function getReflectedClass() + { + return $this->class; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php new file mode 100644 index 0000000000..5bc826d75e --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php @@ -0,0 +1,33 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +class ClassNotFoundException extends DoubleException +{ + private $classname; + + /** + * @param string $message + * @param string $classname + */ + public function __construct($message, $classname) + { + parent::__construct($message); + + $this->classname = $classname; + } + + public function getClassname() + { + return $this->classname; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php new file mode 100644 index 0000000000..6642a58f20 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php @@ -0,0 +1,18 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +use RuntimeException; + +class DoubleException extends RuntimeException implements DoublerException +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php new file mode 100644 index 0000000000..9d6be17969 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php @@ -0,0 +1,18 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +use Prophecy\Exception\Exception; + +interface DoublerException extends Exception +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php new file mode 100644 index 0000000000..e344dead2b --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php @@ -0,0 +1,20 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +class InterfaceNotFoundException extends ClassNotFoundException +{ + public function getInterfaceName() + { + return $this->getClassname(); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php new file mode 100644 index 0000000000..56f47b1105 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php @@ -0,0 +1,41 @@ +methodName = $methodName; + $this->className = $className; + } + + + /** + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * @return string + */ + public function getClassName() + { + return $this->className; + } + + } diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php new file mode 100644 index 0000000000..a538349480 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php @@ -0,0 +1,60 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +class MethodNotFoundException extends DoubleException +{ + /** + * @var string|object + */ + private $classname; + + /** + * @var string + */ + private $methodName; + + /** + * @var array + */ + private $arguments; + + /** + * @param string $message + * @param string|object $classname + * @param string $methodName + * @param null|Argument\ArgumentsWildcard|array $arguments + */ + public function __construct($message, $classname, $methodName, $arguments = null) + { + parent::__construct($message); + + $this->classname = $classname; + $this->methodName = $methodName; + $this->arguments = $arguments; + } + + public function getClassname() + { + return $this->classname; + } + + public function getMethodName() + { + return $this->methodName; + } + + public function getArguments() + { + return $this->arguments; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php new file mode 100644 index 0000000000..6303049700 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php @@ -0,0 +1,41 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Doubler; + +class ReturnByReferenceException extends DoubleException +{ + private $classname; + private $methodName; + + /** + * @param string $message + * @param string $classname + * @param string $methodName + */ + public function __construct($message, $classname, $methodName) + { + parent::__construct($message); + + $this->classname = $classname; + $this->methodName = $methodName; + } + + public function getClassname() + { + return $this->classname; + } + + public function getMethodName() + { + return $this->methodName; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Exception.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Exception.php new file mode 100644 index 0000000000..ac9fe4dd99 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Exception.php @@ -0,0 +1,26 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception; + +/** + * Core Prophecy exception interface. + * All Prophecy exceptions implement it. + * + * @author Konstantin Kudryashov + */ +interface Exception +{ + /** + * @return string + */ + public function getMessage(); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..bc91c690fa --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php new file mode 100644 index 0000000000..44b598a440 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php @@ -0,0 +1,50 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prediction; + +use Prophecy\Prophecy\ObjectProphecy; + +class AggregateException extends \RuntimeException implements PredictionException +{ + private $exceptions = array(); + private $objectProphecy; + + public function append(PredictionException $exception) + { + $message = $exception->getMessage(); + $message = ' '.strtr($message, array("\n" => "\n "))."\n"; + + $this->message = rtrim($this->message.$message); + $this->exceptions[] = $exception; + } + + /** + * @return PredictionException[] + */ + public function getExceptions() + { + return $this->exceptions; + } + + public function setObjectProphecy(ObjectProphecy $objectProphecy) + { + $this->objectProphecy = $objectProphecy; + } + + /** + * @return ObjectProphecy + */ + public function getObjectProphecy() + { + return $this->objectProphecy; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php new file mode 100644 index 0000000000..bbbbc3d97a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php @@ -0,0 +1,24 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prediction; + +use RuntimeException; + +/** + * Basic failed prediction exception. + * Use it for custom prediction failures. + * + * @author Konstantin Kudryashov + */ +class FailedPredictionException extends RuntimeException implements PredictionException +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php new file mode 100644 index 0000000000..05ea4aad86 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php @@ -0,0 +1,18 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prediction; + +use Prophecy\Exception\Prophecy\MethodProphecyException; + +class NoCallsException extends MethodProphecyException implements PredictionException +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php new file mode 100644 index 0000000000..2596b1ef1f --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php @@ -0,0 +1,18 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prediction; + +use Prophecy\Exception\Exception; + +interface PredictionException extends Exception +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php new file mode 100644 index 0000000000..9d905431f8 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php @@ -0,0 +1,31 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prediction; + +use Prophecy\Prophecy\MethodProphecy; + +class UnexpectedCallsCountException extends UnexpectedCallsException +{ + private $expectedCount; + + public function __construct($message, MethodProphecy $methodProphecy, $count, array $calls) + { + parent::__construct($message, $methodProphecy, $calls); + + $this->expectedCount = intval($count); + } + + public function getExpectedCount() + { + return $this->expectedCount; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php new file mode 100644 index 0000000000..7a99c2d796 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php @@ -0,0 +1,32 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prediction; + +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\Prophecy\MethodProphecyException; + +class UnexpectedCallsException extends MethodProphecyException implements PredictionException +{ + private $calls = array(); + + public function __construct($message, MethodProphecy $methodProphecy, array $calls) + { + parent::__construct($message, $methodProphecy); + + $this->calls = $calls; + } + + public function getCalls() + { + return $this->calls; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php new file mode 100644 index 0000000000..1b03eaf472 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php @@ -0,0 +1,34 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prophecy; + +use Prophecy\Prophecy\MethodProphecy; + +class MethodProphecyException extends ObjectProphecyException +{ + private $methodProphecy; + + public function __construct($message, MethodProphecy $methodProphecy) + { + parent::__construct($message, $methodProphecy->getObjectProphecy()); + + $this->methodProphecy = $methodProphecy; + } + + /** + * @return MethodProphecy + */ + public function getMethodProphecy() + { + return $this->methodProphecy; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php new file mode 100644 index 0000000000..e345402e01 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php @@ -0,0 +1,34 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prophecy; + +use Prophecy\Prophecy\ObjectProphecy; + +class ObjectProphecyException extends \RuntimeException implements ProphecyException +{ + private $objectProphecy; + + public function __construct($message, ObjectProphecy $objectProphecy) + { + parent::__construct($message); + + $this->objectProphecy = $objectProphecy; + } + + /** + * @return ObjectProphecy + */ + public function getObjectProphecy() + { + return $this->objectProphecy; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php new file mode 100644 index 0000000000..9157332872 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php @@ -0,0 +1,18 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Exception\Prophecy; + +use Prophecy\Exception\Exception; + +interface ProphecyException extends Exception +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php new file mode 100644 index 0000000000..209821ce91 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php @@ -0,0 +1,69 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\PhpDocumentor; + +use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; +use phpDocumentor\Reflection\DocBlock\Tags\Method; + +/** + * @author Théo FIDRY + * + * @internal + */ +final class ClassAndInterfaceTagRetriever implements MethodTagRetrieverInterface +{ + private $classRetriever; + + public function __construct(MethodTagRetrieverInterface $classRetriever = null) + { + if (null !== $classRetriever) { + $this->classRetriever = $classRetriever; + + return; + } + + $this->classRetriever = class_exists('phpDocumentor\Reflection\DocBlockFactory') && class_exists('phpDocumentor\Reflection\Types\ContextFactory') + ? new ClassTagRetriever() + : new LegacyClassTagRetriever() + ; + } + + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[]|Method[] + */ + public function getTagList(\ReflectionClass $reflectionClass) + { + return array_merge( + $this->classRetriever->getTagList($reflectionClass), + $this->getInterfacesTagList($reflectionClass) + ); + } + + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[]|Method[] + */ + private function getInterfacesTagList(\ReflectionClass $reflectionClass) + { + $interfaces = $reflectionClass->getInterfaces(); + $tagList = array(); + + foreach($interfaces as $interface) { + $tagList = array_merge($tagList, $this->classRetriever->getTagList($interface)); + } + + return $tagList; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php new file mode 100644 index 0000000000..1d2da8f03e --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php @@ -0,0 +1,52 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\PhpDocumentor; + +use phpDocumentor\Reflection\DocBlock\Tags\Method; +use phpDocumentor\Reflection\DocBlockFactory; +use phpDocumentor\Reflection\Types\ContextFactory; + +/** + * @author Théo FIDRY + * + * @internal + */ +final class ClassTagRetriever implements MethodTagRetrieverInterface +{ + private $docBlockFactory; + private $contextFactory; + + public function __construct() + { + $this->docBlockFactory = DocBlockFactory::createInstance(); + $this->contextFactory = new ContextFactory(); + } + + /** + * @param \ReflectionClass $reflectionClass + * + * @return Method[] + */ + public function getTagList(\ReflectionClass $reflectionClass) + { + try { + $phpdoc = $this->docBlockFactory->create( + $reflectionClass, + $this->contextFactory->createFromReflector($reflectionClass) + ); + + return $phpdoc->getTagsByName('method'); + } catch (\InvalidArgumentException $e) { + return array(); + } + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php new file mode 100644 index 0000000000..c0dec3de82 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php @@ -0,0 +1,35 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\PhpDocumentor; + +use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; + +/** + * @author Théo FIDRY + * + * @internal + */ +final class LegacyClassTagRetriever implements MethodTagRetrieverInterface +{ + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[] + */ + public function getTagList(\ReflectionClass $reflectionClass) + { + $phpdoc = new DocBlock($reflectionClass->getDocComment()); + + return $phpdoc->getTagsByName('method'); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php new file mode 100644 index 0000000000..d3989dad58 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php @@ -0,0 +1,30 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\PhpDocumentor; + +use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag; +use phpDocumentor\Reflection\DocBlock\Tags\Method; + +/** + * @author Théo FIDRY + * + * @internal + */ +interface MethodTagRetrieverInterface +{ + /** + * @param \ReflectionClass $reflectionClass + * + * @return LegacyMethodTag[]|Method[] + */ + public function getTagList(\ReflectionClass $reflectionClass); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php b/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php new file mode 100644 index 0000000000..b478736695 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php @@ -0,0 +1,86 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prediction; + +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Argument\Token\AnyValuesToken; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\NoCallsException; + +/** + * Call prediction. + * + * @author Konstantin Kudryashov + */ +class CallPrediction implements PredictionInterface +{ + private $util; + + /** + * Initializes prediction. + * + * @param StringUtil $util + */ + public function __construct(StringUtil $util = null) + { + $this->util = $util ?: new StringUtil; + } + + /** + * Tests that there was at least one call. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws \Prophecy\Exception\Prediction\NoCallsException + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + if (count($calls)) { + return; + } + + $methodCalls = $object->findProphecyMethodCalls( + $method->getMethodName(), + new ArgumentsWildcard(array(new AnyValuesToken)) + ); + + if (count($methodCalls)) { + throw new NoCallsException(sprintf( + "No calls have been made that match:\n". + " %s->%s(%s)\n". + "but expected at least one.\n". + "Recorded `%s(...)` calls:\n%s", + + get_class($object->reveal()), + $method->getMethodName(), + $method->getArgumentsWildcard(), + $method->getMethodName(), + $this->util->stringifyCalls($methodCalls) + ), $method); + } + + throw new NoCallsException(sprintf( + "No calls have been made that match:\n". + " %s->%s(%s)\n". + "but expected at least one.", + + get_class($object->reveal()), + $method->getMethodName(), + $method->getArgumentsWildcard() + ), $method); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php b/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php new file mode 100644 index 0000000000..31c6c575ac --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php @@ -0,0 +1,107 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prediction; + +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Argument\Token\AnyValuesToken; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\UnexpectedCallsCountException; + +/** + * Prediction interface. + * Predictions are logical test blocks, tied to `should...` keyword. + * + * @author Konstantin Kudryashov + */ +class CallTimesPrediction implements PredictionInterface +{ + private $times; + private $util; + + /** + * Initializes prediction. + * + * @param int $times + * @param StringUtil $util + */ + public function __construct($times, StringUtil $util = null) + { + $this->times = intval($times); + $this->util = $util ?: new StringUtil; + } + + /** + * Tests that there was exact amount of calls made. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws \Prophecy\Exception\Prediction\UnexpectedCallsCountException + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + if ($this->times == count($calls)) { + return; + } + + $methodCalls = $object->findProphecyMethodCalls( + $method->getMethodName(), + new ArgumentsWildcard(array(new AnyValuesToken)) + ); + + if (count($calls)) { + $message = sprintf( + "Expected exactly %d calls that match:\n". + " %s->%s(%s)\n". + "but %d were made:\n%s", + + $this->times, + get_class($object->reveal()), + $method->getMethodName(), + $method->getArgumentsWildcard(), + count($calls), + $this->util->stringifyCalls($calls) + ); + } elseif (count($methodCalls)) { + $message = sprintf( + "Expected exactly %d calls that match:\n". + " %s->%s(%s)\n". + "but none were made.\n". + "Recorded `%s(...)` calls:\n%s", + + $this->times, + get_class($object->reveal()), + $method->getMethodName(), + $method->getArgumentsWildcard(), + $method->getMethodName(), + $this->util->stringifyCalls($methodCalls) + ); + } else { + $message = sprintf( + "Expected exactly %d calls that match:\n". + " %s->%s(%s)\n". + "but none were made.", + + $this->times, + get_class($object->reveal()), + $method->getMethodName(), + $method->getArgumentsWildcard() + ); + } + + throw new UnexpectedCallsCountException($message, $method, $this->times, $calls); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php b/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php new file mode 100644 index 0000000000..44bc782c8a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php @@ -0,0 +1,65 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prediction; + +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\InvalidArgumentException; +use Closure; + +/** + * Callback prediction. + * + * @author Konstantin Kudryashov + */ +class CallbackPrediction implements PredictionInterface +{ + private $callback; + + /** + * Initializes callback prediction. + * + * @param callable $callback Custom callback + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($callback) + { + if (!is_callable($callback)) { + throw new InvalidArgumentException(sprintf( + 'Callable expected as an argument to CallbackPrediction, but got %s.', + gettype($callback) + )); + } + + $this->callback = $callback; + } + + /** + * Executes preset callback. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + $callback = $this->callback; + + if ($callback instanceof Closure && method_exists('Closure', 'bind')) { + $callback = Closure::bind($callback, $object); + } + + call_user_func($callback, $calls, $object, $method); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php b/vendor/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php new file mode 100644 index 0000000000..46ac5bfc06 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php @@ -0,0 +1,68 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prediction; + +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\UnexpectedCallsException; + +/** + * No calls prediction. + * + * @author Konstantin Kudryashov + */ +class NoCallsPrediction implements PredictionInterface +{ + private $util; + + /** + * Initializes prediction. + * + * @param null|StringUtil $util + */ + public function __construct(StringUtil $util = null) + { + $this->util = $util ?: new StringUtil; + } + + /** + * Tests that there were no calls made. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws \Prophecy\Exception\Prediction\UnexpectedCallsException + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method) + { + if (!count($calls)) { + return; + } + + $verb = count($calls) === 1 ? 'was' : 'were'; + + throw new UnexpectedCallsException(sprintf( + "No calls expected that match:\n". + " %s->%s(%s)\n". + "but %d %s made:\n%s", + get_class($object->reveal()), + $method->getMethodName(), + $method->getArgumentsWildcard(), + count($calls), + $verb, + $this->util->stringifyCalls($calls) + ), $method, $calls); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php new file mode 100644 index 0000000000..f7fb06a996 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php @@ -0,0 +1,37 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prediction; + +use Prophecy\Call\Call; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; + +/** + * Prediction interface. + * Predictions are logical test blocks, tied to `should...` keyword. + * + * @author Konstantin Kudryashov + */ +interface PredictionInterface +{ + /** + * Tests that double fulfilled prediction. + * + * @param Call[] $calls + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws object + * @return void + */ + public function check(array $calls, ObjectProphecy $object, MethodProphecy $method); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php b/vendor/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php new file mode 100644 index 0000000000..5f406bf7a8 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php @@ -0,0 +1,66 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Promise; + +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\InvalidArgumentException; +use Closure; + +/** + * Callback promise. + * + * @author Konstantin Kudryashov + */ +class CallbackPromise implements PromiseInterface +{ + private $callback; + + /** + * Initializes callback promise. + * + * @param callable $callback Custom callback + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($callback) + { + if (!is_callable($callback)) { + throw new InvalidArgumentException(sprintf( + 'Callable expected as an argument to CallbackPromise, but got %s.', + gettype($callback) + )); + } + + $this->callback = $callback; + } + + /** + * Evaluates promise callback. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + $callback = $this->callback; + + if ($callback instanceof Closure && method_exists('Closure', 'bind')) { + $callback = Closure::bind($callback, $object); + } + + return call_user_func($callback, $args, $object, $method); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php new file mode 100644 index 0000000000..382537b47d --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php @@ -0,0 +1,35 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Promise; + +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; + +/** + * Promise interface. + * Promises are logical blocks, tied to `will...` keyword. + * + * @author Konstantin Kudryashov + */ +interface PromiseInterface +{ + /** + * Evaluates promise. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php b/vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php new file mode 100644 index 0000000000..39bfeea070 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php @@ -0,0 +1,61 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Promise; + +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; + +/** + * Return argument promise. + * + * @author Konstantin Kudryashov + */ +class ReturnArgumentPromise implements PromiseInterface +{ + /** + * @var int + */ + private $index; + + /** + * Initializes callback promise. + * + * @param int $index The zero-indexed number of the argument to return + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($index = 0) + { + if (!is_int($index) || $index < 0) { + throw new InvalidArgumentException(sprintf( + 'Zero-based index expected as argument to ReturnArgumentPromise, but got %s.', + $index + )); + } + $this->index = $index; + } + + /** + * Returns nth argument if has one, null otherwise. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return null|mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + return count($args) > $this->index ? $args[$this->index] : null; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php b/vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php new file mode 100644 index 0000000000..c7d5ac5988 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php @@ -0,0 +1,55 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Promise; + +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; + +/** + * Return promise. + * + * @author Konstantin Kudryashov + */ +class ReturnPromise implements PromiseInterface +{ + private $returnValues = array(); + + /** + * Initializes promise. + * + * @param array $returnValues Array of values + */ + public function __construct(array $returnValues) + { + $this->returnValues = $returnValues; + } + + /** + * Returns saved values one by one until last one, then continuously returns last value. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @return mixed + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + $value = array_shift($this->returnValues); + + if (!count($this->returnValues)) { + $this->returnValues[] = $value; + } + + return $value; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php b/vendor/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php new file mode 100644 index 0000000000..7250fa3c6d --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php @@ -0,0 +1,99 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Promise; + +use Doctrine\Instantiator\Instantiator; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\MethodProphecy; +use Prophecy\Exception\InvalidArgumentException; +use ReflectionClass; + +/** + * Throw promise. + * + * @author Konstantin Kudryashov + */ +class ThrowPromise implements PromiseInterface +{ + private $exception; + + /** + * @var \Doctrine\Instantiator\Instantiator + */ + private $instantiator; + + /** + * Initializes promise. + * + * @param string|\Exception|\Throwable $exception Exception class name or instance + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function __construct($exception) + { + if (is_string($exception)) { + if (!class_exists($exception) || !$this->isAValidThrowable($exception)) { + throw new InvalidArgumentException(sprintf( + 'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', + $exception + )); + } + } elseif (!$exception instanceof \Exception && !$exception instanceof \Throwable) { + throw new InvalidArgumentException(sprintf( + 'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.', + is_object($exception) ? get_class($exception) : gettype($exception) + )); + } + + $this->exception = $exception; + } + + /** + * Throws predefined exception. + * + * @param array $args + * @param ObjectProphecy $object + * @param MethodProphecy $method + * + * @throws object + */ + public function execute(array $args, ObjectProphecy $object, MethodProphecy $method) + { + if (is_string($this->exception)) { + $classname = $this->exception; + $reflection = new ReflectionClass($classname); + $constructor = $reflection->getConstructor(); + + if ($constructor->isPublic() && 0 == $constructor->getNumberOfRequiredParameters()) { + throw $reflection->newInstance(); + } + + if (!$this->instantiator) { + $this->instantiator = new Instantiator(); + } + + throw $this->instantiator->instantiate($classname); + } + + throw $this->exception; + } + + /** + * @param string $exception + * + * @return bool + */ + private function isAValidThrowable($exception) + { + return is_a($exception, 'Exception', true) || is_subclass_of($exception, 'Throwable', true); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php new file mode 100644 index 0000000000..90df1efc8c --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php @@ -0,0 +1,464 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prophecy; + +use Prophecy\Argument; +use Prophecy\Prophet; +use Prophecy\Promise; +use Prophecy\Prediction; +use Prophecy\Exception\Doubler\MethodNotFoundException; +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Exception\Prophecy\MethodProphecyException; + +/** + * Method prophecy. + * + * @author Konstantin Kudryashov + */ +class MethodProphecy +{ + private $objectProphecy; + private $methodName; + private $argumentsWildcard; + private $promise; + private $prediction; + private $checkedPredictions = array(); + private $bound = false; + private $voidReturnType = false; + + /** + * Initializes method prophecy. + * + * @param ObjectProphecy $objectProphecy + * @param string $methodName + * @param null|Argument\ArgumentsWildcard|array $arguments + * + * @throws \Prophecy\Exception\Doubler\MethodNotFoundException If method not found + */ + public function __construct(ObjectProphecy $objectProphecy, $methodName, $arguments = null) + { + $double = $objectProphecy->reveal(); + if (!method_exists($double, $methodName)) { + throw new MethodNotFoundException(sprintf( + 'Method `%s::%s()` is not defined.', get_class($double), $methodName + ), get_class($double), $methodName, $arguments); + } + + $this->objectProphecy = $objectProphecy; + $this->methodName = $methodName; + + $reflectedMethod = new \ReflectionMethod($double, $methodName); + if ($reflectedMethod->isFinal()) { + throw new MethodProphecyException(sprintf( + "Can not add prophecy for a method `%s::%s()`\n". + "as it is a final method.", + get_class($double), + $methodName + ), $this); + } + + if (null !== $arguments) { + $this->withArguments($arguments); + } + + if (version_compare(PHP_VERSION, '7.0', '>=') && true === $reflectedMethod->hasReturnType()) { + $type = (string) $reflectedMethod->getReturnType(); + + if ('void' === $type) { + $this->voidReturnType = true; + return; + } + + $this->will(function () use ($type) { + switch ($type) { + case 'string': return ''; + case 'float': return 0.0; + case 'int': return 0; + case 'bool': return false; + case 'array': return array(); + + case 'callable': + case 'Closure': + return function () {}; + + case 'Traversable': + case 'Generator': + // Remove eval() when minimum version >=5.5 + /** @var callable $generator */ + $generator = eval('return function () { yield; };'); + return $generator(); + + default: + $prophet = new Prophet; + return $prophet->prophesize($type)->reveal(); + } + }); + } + } + + /** + * Sets argument wildcard. + * + * @param array|Argument\ArgumentsWildcard $arguments + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function withArguments($arguments) + { + if (is_array($arguments)) { + $arguments = new Argument\ArgumentsWildcard($arguments); + } + + if (!$arguments instanceof Argument\ArgumentsWildcard) { + throw new InvalidArgumentException(sprintf( + "Either an array or an instance of ArgumentsWildcard expected as\n". + 'a `MethodProphecy::withArguments()` argument, but got %s.', + gettype($arguments) + )); + } + + $this->argumentsWildcard = $arguments; + + return $this; + } + + /** + * Sets custom promise to the prophecy. + * + * @param callable|Promise\PromiseInterface $promise + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function will($promise) + { + if (is_callable($promise)) { + $promise = new Promise\CallbackPromise($promise); + } + + if (!$promise instanceof Promise\PromiseInterface) { + throw new InvalidArgumentException(sprintf( + 'Expected callable or instance of PromiseInterface, but got %s.', + gettype($promise) + )); + } + + $this->bindToObjectProphecy(); + $this->promise = $promise; + + return $this; + } + + /** + * Sets return promise to the prophecy. + * + * @see \Prophecy\Promise\ReturnPromise + * + * @return $this + */ + public function willReturn() + { + if ($this->voidReturnType) { + throw new MethodProphecyException( + "The method \"$this->methodName\" has a void return type, and so cannot return anything", + $this + ); + } + + return $this->will(new Promise\ReturnPromise(func_get_args())); + } + + /** + * Sets return argument promise to the prophecy. + * + * @param int $index The zero-indexed number of the argument to return + * + * @see \Prophecy\Promise\ReturnArgumentPromise + * + * @return $this + */ + public function willReturnArgument($index = 0) + { + if ($this->voidReturnType) { + throw new MethodProphecyException("The method \"$this->methodName\" has a void return type", $this); + } + + return $this->will(new Promise\ReturnArgumentPromise($index)); + } + + /** + * Sets throw promise to the prophecy. + * + * @see \Prophecy\Promise\ThrowPromise + * + * @param string|\Exception $exception Exception class or instance + * + * @return $this + */ + public function willThrow($exception) + { + return $this->will(new Promise\ThrowPromise($exception)); + } + + /** + * Sets custom prediction to the prophecy. + * + * @param callable|Prediction\PredictionInterface $prediction + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function should($prediction) + { + if (is_callable($prediction)) { + $prediction = new Prediction\CallbackPrediction($prediction); + } + + if (!$prediction instanceof Prediction\PredictionInterface) { + throw new InvalidArgumentException(sprintf( + 'Expected callable or instance of PredictionInterface, but got %s.', + gettype($prediction) + )); + } + + $this->bindToObjectProphecy(); + $this->prediction = $prediction; + + return $this; + } + + /** + * Sets call prediction to the prophecy. + * + * @see \Prophecy\Prediction\CallPrediction + * + * @return $this + */ + public function shouldBeCalled() + { + return $this->should(new Prediction\CallPrediction); + } + + /** + * Sets no calls prediction to the prophecy. + * + * @see \Prophecy\Prediction\NoCallsPrediction + * + * @return $this + */ + public function shouldNotBeCalled() + { + return $this->should(new Prediction\NoCallsPrediction); + } + + /** + * Sets call times prediction to the prophecy. + * + * @see \Prophecy\Prediction\CallTimesPrediction + * + * @param $count + * + * @return $this + */ + public function shouldBeCalledTimes($count) + { + return $this->should(new Prediction\CallTimesPrediction($count)); + } + + /** + * Checks provided prediction immediately. + * + * @param callable|Prediction\PredictionInterface $prediction + * + * @return $this + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function shouldHave($prediction) + { + if (is_callable($prediction)) { + $prediction = new Prediction\CallbackPrediction($prediction); + } + + if (!$prediction instanceof Prediction\PredictionInterface) { + throw new InvalidArgumentException(sprintf( + 'Expected callable or instance of PredictionInterface, but got %s.', + gettype($prediction) + )); + } + + if (null === $this->promise && !$this->voidReturnType) { + $this->willReturn(); + } + + $calls = $this->getObjectProphecy()->findProphecyMethodCalls( + $this->getMethodName(), + $this->getArgumentsWildcard() + ); + + try { + $prediction->check($calls, $this->getObjectProphecy(), $this); + $this->checkedPredictions[] = $prediction; + } catch (\Exception $e) { + $this->checkedPredictions[] = $prediction; + + throw $e; + } + + return $this; + } + + /** + * Checks call prediction. + * + * @see \Prophecy\Prediction\CallPrediction + * + * @return $this + */ + public function shouldHaveBeenCalled() + { + return $this->shouldHave(new Prediction\CallPrediction); + } + + /** + * Checks no calls prediction. + * + * @see \Prophecy\Prediction\NoCallsPrediction + * + * @return $this + */ + public function shouldNotHaveBeenCalled() + { + return $this->shouldHave(new Prediction\NoCallsPrediction); + } + + /** + * Checks no calls prediction. + * + * @see \Prophecy\Prediction\NoCallsPrediction + * @deprecated + * + * @return $this + */ + public function shouldNotBeenCalled() + { + return $this->shouldNotHaveBeenCalled(); + } + + /** + * Checks call times prediction. + * + * @see \Prophecy\Prediction\CallTimesPrediction + * + * @param int $count + * + * @return $this + */ + public function shouldHaveBeenCalledTimes($count) + { + return $this->shouldHave(new Prediction\CallTimesPrediction($count)); + } + + /** + * Checks currently registered [with should(...)] prediction. + */ + public function checkPrediction() + { + if (null === $this->prediction) { + return; + } + + $this->shouldHave($this->prediction); + } + + /** + * Returns currently registered promise. + * + * @return null|Promise\PromiseInterface + */ + public function getPromise() + { + return $this->promise; + } + + /** + * Returns currently registered prediction. + * + * @return null|Prediction\PredictionInterface + */ + public function getPrediction() + { + return $this->prediction; + } + + /** + * Returns predictions that were checked on this object. + * + * @return Prediction\PredictionInterface[] + */ + public function getCheckedPredictions() + { + return $this->checkedPredictions; + } + + /** + * Returns object prophecy this method prophecy is tied to. + * + * @return ObjectProphecy + */ + public function getObjectProphecy() + { + return $this->objectProphecy; + } + + /** + * Returns method name. + * + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * Returns arguments wildcard. + * + * @return Argument\ArgumentsWildcard + */ + public function getArgumentsWildcard() + { + return $this->argumentsWildcard; + } + + /** + * @return bool + */ + public function hasReturnVoid() + { + return $this->voidReturnType; + } + + private function bindToObjectProphecy() + { + if ($this->bound) { + return; + } + + $this->getObjectProphecy()->addMethodProphecy($this); + $this->bound = true; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php new file mode 100644 index 0000000000..8d8f8a1bb9 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php @@ -0,0 +1,281 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prophecy; + +use SebastianBergmann\Comparator\ComparisonFailure; +use Prophecy\Comparator\Factory as ComparatorFactory; +use Prophecy\Call\Call; +use Prophecy\Doubler\LazyDouble; +use Prophecy\Argument\ArgumentsWildcard; +use Prophecy\Call\CallCenter; +use Prophecy\Exception\Prophecy\ObjectProphecyException; +use Prophecy\Exception\Prophecy\MethodProphecyException; +use Prophecy\Exception\Prediction\AggregateException; +use Prophecy\Exception\Prediction\PredictionException; + +/** + * Object prophecy. + * + * @author Konstantin Kudryashov + */ +class ObjectProphecy implements ProphecyInterface +{ + private $lazyDouble; + private $callCenter; + private $revealer; + private $comparatorFactory; + + /** + * @var MethodProphecy[][] + */ + private $methodProphecies = array(); + + /** + * Initializes object prophecy. + * + * @param LazyDouble $lazyDouble + * @param CallCenter $callCenter + * @param RevealerInterface $revealer + * @param ComparatorFactory $comparatorFactory + */ + public function __construct( + LazyDouble $lazyDouble, + CallCenter $callCenter = null, + RevealerInterface $revealer = null, + ComparatorFactory $comparatorFactory = null + ) { + $this->lazyDouble = $lazyDouble; + $this->callCenter = $callCenter ?: new CallCenter; + $this->revealer = $revealer ?: new Revealer; + + $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance(); + } + + /** + * Forces double to extend specific class. + * + * @param string $class + * + * @return $this + */ + public function willExtend($class) + { + $this->lazyDouble->setParentClass($class); + + return $this; + } + + /** + * Forces double to implement specific interface. + * + * @param string $interface + * + * @return $this + */ + public function willImplement($interface) + { + $this->lazyDouble->addInterface($interface); + + return $this; + } + + /** + * Sets constructor arguments. + * + * @param array $arguments + * + * @return $this + */ + public function willBeConstructedWith(array $arguments = null) + { + $this->lazyDouble->setArguments($arguments); + + return $this; + } + + /** + * Reveals double. + * + * @return object + * + * @throws \Prophecy\Exception\Prophecy\ObjectProphecyException If double doesn't implement needed interface + */ + public function reveal() + { + $double = $this->lazyDouble->getInstance(); + + if (null === $double || !$double instanceof ProphecySubjectInterface) { + throw new ObjectProphecyException( + "Generated double must implement ProphecySubjectInterface, but it does not.\n". + 'It seems you have wrongly configured doubler without required ClassPatch.', + $this + ); + } + + $double->setProphecy($this); + + return $double; + } + + /** + * Adds method prophecy to object prophecy. + * + * @param MethodProphecy $methodProphecy + * + * @throws \Prophecy\Exception\Prophecy\MethodProphecyException If method prophecy doesn't + * have arguments wildcard + */ + public function addMethodProphecy(MethodProphecy $methodProphecy) + { + $argumentsWildcard = $methodProphecy->getArgumentsWildcard(); + if (null === $argumentsWildcard) { + throw new MethodProphecyException(sprintf( + "Can not add prophecy for a method `%s::%s()`\n". + "as you did not specify arguments wildcard for it.", + get_class($this->reveal()), + $methodProphecy->getMethodName() + ), $methodProphecy); + } + + $methodName = $methodProphecy->getMethodName(); + + if (!isset($this->methodProphecies[$methodName])) { + $this->methodProphecies[$methodName] = array(); + } + + $this->methodProphecies[$methodName][] = $methodProphecy; + } + + /** + * Returns either all or related to single method prophecies. + * + * @param null|string $methodName + * + * @return MethodProphecy[] + */ + public function getMethodProphecies($methodName = null) + { + if (null === $methodName) { + return $this->methodProphecies; + } + + if (!isset($this->methodProphecies[$methodName])) { + return array(); + } + + return $this->methodProphecies[$methodName]; + } + + /** + * Makes specific method call. + * + * @param string $methodName + * @param array $arguments + * + * @return mixed + */ + public function makeProphecyMethodCall($methodName, array $arguments) + { + $arguments = $this->revealer->reveal($arguments); + $return = $this->callCenter->makeCall($this, $methodName, $arguments); + + return $this->revealer->reveal($return); + } + + /** + * Finds calls by method name & arguments wildcard. + * + * @param string $methodName + * @param ArgumentsWildcard $wildcard + * + * @return Call[] + */ + public function findProphecyMethodCalls($methodName, ArgumentsWildcard $wildcard) + { + return $this->callCenter->findCalls($methodName, $wildcard); + } + + /** + * Checks that registered method predictions do not fail. + * + * @throws \Prophecy\Exception\Prediction\AggregateException If any of registered predictions fail + */ + public function checkProphecyMethodsPredictions() + { + $exception = new AggregateException(sprintf("%s:\n", get_class($this->reveal()))); + $exception->setObjectProphecy($this); + + foreach ($this->methodProphecies as $prophecies) { + foreach ($prophecies as $prophecy) { + try { + $prophecy->checkPrediction(); + } catch (PredictionException $e) { + $exception->append($e); + } + } + } + + if (count($exception->getExceptions())) { + throw $exception; + } + } + + /** + * Creates new method prophecy using specified method name and arguments. + * + * @param string $methodName + * @param array $arguments + * + * @return MethodProphecy + */ + public function __call($methodName, array $arguments) + { + $arguments = new ArgumentsWildcard($this->revealer->reveal($arguments)); + + foreach ($this->getMethodProphecies($methodName) as $prophecy) { + $argumentsWildcard = $prophecy->getArgumentsWildcard(); + $comparator = $this->comparatorFactory->getComparatorFor( + $argumentsWildcard, $arguments + ); + + try { + $comparator->assertEquals($argumentsWildcard, $arguments); + return $prophecy; + } catch (ComparisonFailure $failure) {} + } + + return new MethodProphecy($this, $methodName, $arguments); + } + + /** + * Tries to get property value from double. + * + * @param string $name + * + * @return mixed + */ + public function __get($name) + { + return $this->reveal()->$name; + } + + /** + * Tries to set property value to double. + * + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->reveal()->$name = $this->revealer->reveal($value); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php new file mode 100644 index 0000000000..462f15a902 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php @@ -0,0 +1,27 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prophecy; + +/** + * Core Prophecy interface. + * + * @author Konstantin Kudryashov + */ +interface ProphecyInterface +{ + /** + * Reveals prophecy object (double) . + * + * @return object + */ + public function reveal(); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php new file mode 100644 index 0000000000..2d839585f9 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php @@ -0,0 +1,34 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prophecy; + +/** + * Controllable doubles interface. + * + * @author Konstantin Kudryashov + */ +interface ProphecySubjectInterface +{ + /** + * Sets subject prophecy. + * + * @param ProphecyInterface $prophecy + */ + public function setProphecy(ProphecyInterface $prophecy); + + /** + * Returns subject prophecy. + * + * @return ProphecyInterface + */ + public function getProphecy(); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php new file mode 100644 index 0000000000..60ecdac814 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php @@ -0,0 +1,44 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prophecy; + +/** + * Basic prophecies revealer. + * + * @author Konstantin Kudryashov + */ +class Revealer implements RevealerInterface +{ + /** + * Unwraps value(s). + * + * @param mixed $value + * + * @return mixed + */ + public function reveal($value) + { + if (is_array($value)) { + return array_map(array($this, __FUNCTION__), $value); + } + + if (!is_object($value)) { + return $value; + } + + if ($value instanceof ProphecyInterface) { + $value = $value->reveal(); + } + + return $value; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php new file mode 100644 index 0000000000..ffc82bb6f5 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php @@ -0,0 +1,29 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Prophecy; + +/** + * Prophecies revealer interface. + * + * @author Konstantin Kudryashov + */ +interface RevealerInterface +{ + /** + * Unwraps value(s). + * + * @param mixed $value + * + * @return mixed + */ + public function reveal($value); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Prophet.php b/vendor/phpspec/prophecy/src/Prophecy/Prophet.php new file mode 100644 index 0000000000..ac64923484 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Prophet.php @@ -0,0 +1,134 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy; + +use Prophecy\Doubler\Doubler; +use Prophecy\Doubler\LazyDouble; +use Prophecy\Doubler\ClassPatch; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophecy\RevealerInterface; +use Prophecy\Prophecy\Revealer; +use Prophecy\Call\CallCenter; +use Prophecy\Util\StringUtil; +use Prophecy\Exception\Prediction\PredictionException; +use Prophecy\Exception\Prediction\AggregateException; + +/** + * Prophet creates prophecies. + * + * @author Konstantin Kudryashov + */ +class Prophet +{ + private $doubler; + private $revealer; + private $util; + + /** + * @var ObjectProphecy[] + */ + private $prophecies = array(); + + /** + * Initializes Prophet. + * + * @param null|Doubler $doubler + * @param null|RevealerInterface $revealer + * @param null|StringUtil $util + */ + public function __construct(Doubler $doubler = null, RevealerInterface $revealer = null, + StringUtil $util = null) + { + if (null === $doubler) { + $doubler = new Doubler; + $doubler->registerClassPatch(new ClassPatch\SplFileInfoPatch); + $doubler->registerClassPatch(new ClassPatch\TraversablePatch); + $doubler->registerClassPatch(new ClassPatch\DisableConstructorPatch); + $doubler->registerClassPatch(new ClassPatch\ProphecySubjectPatch); + $doubler->registerClassPatch(new ClassPatch\ReflectionClassNewInstancePatch); + $doubler->registerClassPatch(new ClassPatch\HhvmExceptionPatch()); + $doubler->registerClassPatch(new ClassPatch\MagicCallPatch); + $doubler->registerClassPatch(new ClassPatch\KeywordPatch); + } + + $this->doubler = $doubler; + $this->revealer = $revealer ?: new Revealer; + $this->util = $util ?: new StringUtil; + } + + /** + * Creates new object prophecy. + * + * @param null|string $classOrInterface Class or interface name + * + * @return ObjectProphecy + */ + public function prophesize($classOrInterface = null) + { + $this->prophecies[] = $prophecy = new ObjectProphecy( + new LazyDouble($this->doubler), + new CallCenter($this->util), + $this->revealer + ); + + if ($classOrInterface && class_exists($classOrInterface)) { + return $prophecy->willExtend($classOrInterface); + } + + if ($classOrInterface && interface_exists($classOrInterface)) { + return $prophecy->willImplement($classOrInterface); + } + + return $prophecy; + } + + /** + * Returns all created object prophecies. + * + * @return ObjectProphecy[] + */ + public function getProphecies() + { + return $this->prophecies; + } + + /** + * Returns Doubler instance assigned to this Prophet. + * + * @return Doubler + */ + public function getDoubler() + { + return $this->doubler; + } + + /** + * Checks all predictions defined by prophecies of this Prophet. + * + * @throws Exception\Prediction\AggregateException If any prediction fails + */ + public function checkPredictions() + { + $exception = new AggregateException("Some predictions failed:\n"); + foreach ($this->prophecies as $prophecy) { + try { + $prophecy->checkProphecyMethodsPredictions(); + } catch (PredictionException $e) { + $exception->append($e); + } + } + + if (count($exception->getExceptions())) { + throw $exception; + } + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php b/vendor/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php new file mode 100644 index 0000000000..50dd3f325e --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php @@ -0,0 +1,212 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * This class is a modification from sebastianbergmann/exporter + * @see https://github.com/sebastianbergmann/exporter + */ +class ExportUtil +{ + /** + * Exports a value as a string + * + * The output of this method is similar to the output of print_r(), but + * improved in various aspects: + * + * - NULL is rendered as "null" (instead of "") + * - TRUE is rendered as "true" (instead of "1") + * - FALSE is rendered as "false" (instead of "") + * - Strings are always quoted with single quotes + * - Carriage returns and newlines are normalized to \n + * - Recursion and repeated rendering is treated properly + * + * @param mixed $value + * @param int $indentation The indentation level of the 2nd+ line + * @return string + */ + public static function export($value, $indentation = 0) + { + return self::recursiveExport($value, $indentation); + } + + /** + * Converts an object to an array containing all of its private, protected + * and public properties. + * + * @param mixed $value + * @return array + */ + public static function toArray($value) + { + if (!is_object($value)) { + return (array) $value; + } + + $array = array(); + + foreach ((array) $value as $key => $val) { + // properties are transformed to keys in the following way: + // private $property => "\0Classname\0property" + // protected $property => "\0*\0property" + // public $property => "property" + if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) { + $key = $matches[1]; + } + + // See https://github.com/php/php-src/commit/5721132 + if ($key === "\0gcdata") { + continue; + } + + $array[$key] = $val; + } + + // Some internal classes like SplObjectStorage don't work with the + // above (fast) mechanism nor with reflection in Zend. + // Format the output similarly to print_r() in this case + if ($value instanceof \SplObjectStorage) { + // However, the fast method does work in HHVM, and exposes the + // internal implementation. Hide it again. + if (property_exists('\SplObjectStorage', '__storage')) { + unset($array['__storage']); + } elseif (property_exists('\SplObjectStorage', 'storage')) { + unset($array['storage']); + } + + if (property_exists('\SplObjectStorage', '__key')) { + unset($array['__key']); + } + + foreach ($value as $key => $val) { + $array[spl_object_hash($val)] = array( + 'obj' => $val, + 'inf' => $value->getInfo(), + ); + } + } + + return $array; + } + + /** + * Recursive implementation of export + * + * @param mixed $value The value to export + * @param int $indentation The indentation level of the 2nd+ line + * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects + * @return string + * @see SebastianBergmann\Exporter\Exporter::export + */ + protected static function recursiveExport(&$value, $indentation, $processed = null) + { + if ($value === null) { + return 'null'; + } + + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if (is_float($value) && floatval(intval($value)) === $value) { + return "$value.0"; + } + + if (is_resource($value)) { + return sprintf( + 'resource(%d) of type (%s)', + $value, + get_resource_type($value) + ); + } + + if (is_string($value)) { + // Match for most non printable chars somewhat taking multibyte chars into account + if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) { + return 'Binary String: 0x' . bin2hex($value); + } + + return "'" . + str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) . + "'"; + } + + $whitespace = str_repeat(' ', 4 * $indentation); + + if (!$processed) { + $processed = new Context; + } + + if (is_array($value)) { + if (($key = $processed->contains($value)) !== false) { + return 'Array &' . $key; + } + + $array = $value; + $key = $processed->add($value); + $values = ''; + + if (count($array) > 0) { + foreach ($array as $k => $v) { + $values .= sprintf( + '%s %s => %s' . "\n", + $whitespace, + self::recursiveExport($k, $indentation), + self::recursiveExport($value[$k], $indentation + 1, $processed) + ); + } + + $values = "\n" . $values . $whitespace; + } + + return sprintf('Array &%s (%s)', $key, $values); + } + + if (is_object($value)) { + $class = get_class($value); + + if ($value instanceof ProphecyInterface) { + return sprintf('%s Object (*Prophecy*)', $class); + } elseif ($hash = $processed->contains($value)) { + return sprintf('%s:%s Object', $class, $hash); + } + + $hash = $processed->add($value); + $values = ''; + $array = self::toArray($value); + + if (count($array) > 0) { + foreach ($array as $k => $v) { + $values .= sprintf( + '%s %s => %s' . "\n", + $whitespace, + self::recursiveExport($k, $indentation), + self::recursiveExport($v, $indentation + 1, $processed) + ); + } + + $values = "\n" . $values . $whitespace; + } + + return sprintf('%s:%s Object (%s)', $class, $hash, $values); + } + + return var_export($value, true); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Util/StringUtil.php b/vendor/phpspec/prophecy/src/Prophecy/Util/StringUtil.php new file mode 100644 index 0000000000..bb90156a3f --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Util/StringUtil.php @@ -0,0 +1,89 @@ + + * Marcello Duarte + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Util; + +use Prophecy\Call\Call; + +/** + * String utility. + * + * @author Konstantin Kudryashov + */ +class StringUtil +{ + /** + * Stringifies any provided value. + * + * @param mixed $value + * @param boolean $exportObject + * + * @return string + */ + public function stringify($value, $exportObject = true) + { + if (is_array($value)) { + if (range(0, count($value) - 1) === array_keys($value)) { + return '['.implode(', ', array_map(array($this, __FUNCTION__), $value)).']'; + } + + $stringify = array($this, __FUNCTION__); + + return '['.implode(', ', array_map(function ($item, $key) use ($stringify) { + return (is_integer($key) ? $key : '"'.$key.'"'). + ' => '.call_user_func($stringify, $item); + }, $value, array_keys($value))).']'; + } + if (is_resource($value)) { + return get_resource_type($value).':'.$value; + } + if (is_object($value)) { + return $exportObject ? ExportUtil::export($value) : sprintf('%s:%s', get_class($value), spl_object_hash($value)); + } + if (true === $value || false === $value) { + return $value ? 'true' : 'false'; + } + if (is_string($value)) { + $str = sprintf('"%s"', str_replace("\n", '\\n', $value)); + + if (50 <= strlen($str)) { + return substr($str, 0, 50).'"...'; + } + + return $str; + } + if (null === $value) { + return 'null'; + } + + return (string) $value; + } + + /** + * Stringifies provided array of calls. + * + * @param Call[] $calls Array of Call instances + * + * @return string + */ + public function stringifyCalls(array $calls) + { + $self = $this; + + return implode(PHP_EOL, array_map(function (Call $call) use ($self) { + return sprintf(' - %s(%s) @ %s', + $call->getMethodName(), + implode(', ', array_map(array($self, 'stringify'), $call->getArguments())), + str_replace(GETCWD().DIRECTORY_SEPARATOR, '', $call->getCallPlace()) + ); + }, $calls)); + } +} diff --git a/vendor/phpunit/php-code-coverage/.gitattributes b/vendor/phpunit/php-code-coverage/.gitattributes new file mode 100644 index 0000000000..461090b7ec --- /dev/null +++ b/vendor/phpunit/php-code-coverage/.gitattributes @@ -0,0 +1 @@ +*.php diff=php diff --git a/vendor/phpunit/php-code-coverage/.github/CONTRIBUTING.md b/vendor/phpunit/php-code-coverage/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..76a4345856 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +Please refer to [https://github.com/sebastianbergmann/phpunit/blob/master/CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/master/CONTRIBUTING.md) for details on how to contribute to this project. diff --git a/vendor/phpunit/php-code-coverage/.github/ISSUE_TEMPLATE.md b/vendor/phpunit/php-code-coverage/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..dc8e3b02fd --- /dev/null +++ b/vendor/phpunit/php-code-coverage/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ +| Q | A +| --------------------------| --------------- +| php-code-coverage version | x.y.z +| PHP version | x.y.z +| Driver | Xdebug / PHPDBG +| Xdebug version (if used) | x.y.z +| Installation Method | Composer / PHPUnit PHAR +| Usage Method | PHPUnit / other +| PHPUnit version (if used) | x.y.z + + + diff --git a/vendor/phpunit/php-code-coverage/.php_cs b/vendor/phpunit/php-code-coverage/.php_cs new file mode 100644 index 0000000000..de5cde1806 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/.php_cs @@ -0,0 +1,69 @@ +files() + ->in('src') + ->in('tests') + ->exclude('_files') + ->name('*.php'); + +return Symfony\CS\Config\Config::create() + ->setUsingCache(true) + ->level(\Symfony\CS\FixerInterface::NONE_LEVEL) + ->fixers( + array( + 'align_double_arrow', + 'align_equals', + 'braces', + 'concat_with_spaces', + 'duplicate_semicolon', + 'elseif', + 'empty_return', + 'encoding', + 'eof_ending', + 'extra_empty_lines', + 'function_call_space', + 'function_declaration', + 'indentation', + 'join_function', + 'line_after_namespace', + 'linefeed', + 'list_commas', + 'lowercase_constants', + 'lowercase_keywords', + 'method_argument_space', + 'multiple_use', + 'namespace_no_leading_whitespace', + 'no_blank_lines_after_class_opening', + 'no_empty_lines_after_phpdocs', + 'parenthesis', + 'php_closing_tag', + 'phpdoc_indent', + 'phpdoc_no_access', + 'phpdoc_no_empty_return', + 'phpdoc_no_package', + 'phpdoc_params', + 'phpdoc_scalar', + 'phpdoc_separation', + 'phpdoc_to_comment', + 'phpdoc_trim', + 'phpdoc_types', + 'phpdoc_var_without_name', + 'remove_lines_between_uses', + 'return', + 'self_accessor', + 'short_array_syntax', + 'short_tag', + 'single_line_after_imports', + 'single_quote', + 'spaces_before_semicolon', + 'spaces_cast', + 'ternary_spaces', + 'trailing_spaces', + 'trim_array_spaces', + 'unused_use', + 'visibility', + 'whitespacy_lines' + ) + ) + ->finder($finder); + diff --git a/vendor/phpunit/php-code-coverage/.travis.yml b/vendor/phpunit/php-code-coverage/.travis.yml new file mode 100644 index 0000000000..96c63689bf --- /dev/null +++ b/vendor/phpunit/php-code-coverage/.travis.yml @@ -0,0 +1,42 @@ +language: php + +php: + - 5.6 + - 7.0 + - 7.0snapshot + - 7.1 + - 7.1snapshot + - master + +env: + matrix: + - DRIVER="xdebug" + - DRIVER="phpdbg" + +matrix: + allow_failures: + - php: master + fast_finish: true + exclude: + - php: 5.6 + env: DRIVER="phpdbg" + +sudo: false + +before_install: + - composer self-update + - composer clear-cache + +install: + - travis_retry composer update --no-interaction --no-ansi --no-progress --no-suggest --optimize-autoloader --prefer-stable + +script: + - if [[ "$DRIVER" = 'phpdbg' ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi + - if [[ "$DRIVER" = 'xdebug' ]]; then vendor/bin/phpunit --coverage-clover=coverage.xml; fi + +after_success: + - bash <(curl -s https://codecov.io/bash) + +notifications: + email: false + diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-2.2.md b/vendor/phpunit/php-code-coverage/ChangeLog-2.2.md new file mode 100644 index 0000000000..353b6f6500 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-2.2.md @@ -0,0 +1,56 @@ +# Changes in PHP_CodeCoverage 2.2 + +All notable changes of the PHP_CodeCoverage 2.2 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [2.2.4] - 2015-10-06 + +### Fixed + +* Fixed [#391](https://github.com/sebastianbergmann/php-code-coverage/pull/391): Missing `` tag + +## [2.2.3] - 2015-09-14 + +### Fixed + +* Fixed [#368](https://github.com/sebastianbergmann/php-code-coverage/pull/368): Blacklists and whitelists are not merged when merging data sets +* Fixed [#370](https://github.com/sebastianbergmann/php-code-coverage/issues/370): Confusing statistics for source file that declares a class without methods +* Fixed [#372](https://github.com/sebastianbergmann/php-code-coverage/pull/372): Nested classes and functions are not handled correctly +* Fixed [#382](https://github.com/sebastianbergmann/php-code-coverage/issues/382): Crap4J report generates incorrect XML logfile + +## [2.2.2] - 2015-08-04 + +### Added + +* Reintroduced the `PHP_CodeCoverage_Driver_HHVM` driver as an extension of `PHP_CodeCoverage_Driver_Xdebug` that does not use `xdebug_start_code_coverage()` with options not supported by HHVM + +### Changed + +* Bumped required version of `sebastian/environment` to 1.3.2 for [#365](https://github.com/sebastianbergmann/php-code-coverage/issues/365) + +## [2.2.1] - 2015-08-02 + +### Changed + +* Bumped required version of `sebastian/environment` to 1.3.1 for [#365](https://github.com/sebastianbergmann/php-code-coverage/issues/365) + +## [2.2.0] - 2015-08-01 + +### Added + +* Added a driver for PHPDBG (requires PHP 7) +* Added `PHP_CodeCoverage::setDisableIgnoredLines()` to disable the ignoring of lines using annotations such as `@codeCoverageIgnore` + +### Changed + +* Annotating a method with `@deprecated` now has the same effect as annotating it with `@codeCoverageIgnore` + +### Removed + +* The dedicated driver for HHVM, `PHP_CodeCoverage_Driver_HHVM` has been removed + +[2.2.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/2.2.3...2.2.4 +[2.2.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/2.2.2...2.2.3 +[2.2.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/2.2.1...2.2.2 +[2.2.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/2.2.0...2.2.1 +[2.2.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/2.1...2.2.0 + diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-3.0.md b/vendor/phpunit/php-code-coverage/ChangeLog-3.0.md new file mode 100644 index 0000000000..a39fa8d47e --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-3.0.md @@ -0,0 +1,31 @@ +# Changes in PHP_CodeCoverage 3.0 + +All notable changes of the PHP_CodeCoverage 3.0 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [3.0.2] - 2015-11-12 + +### Changed + +* It is now optional that `@deprecated` code is ignored + +## [3.0.1] - 2015-10-06 + +### Fixed + +* Fixed [#391](https://github.com/sebastianbergmann/php-code-coverage/pull/391): Missing `` tag + +## [3.0.0] - 2015-10-02 + +### Changed + +* It is now mandatory to configure a whitelist + +### Removed + +* The blacklist functionality has been removed +* PHP_CodeCoverage is no longer supported on PHP 5.3, PHP 5.4, and PHP 5.5 + +[3.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.0.1...3.0.2 +[3.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/2.2...3.0.0 + diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-3.1.md b/vendor/phpunit/php-code-coverage/ChangeLog-3.1.md new file mode 100644 index 0000000000..f7a0de9040 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-3.1.md @@ -0,0 +1,30 @@ +# Changes in PHP_CodeCoverage 3.1 + +All notable changes of the PHP_CodeCoverage 3.1 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [3.1.1] - 2016-02-04 + +### Changed + +* Allow version 2.0.x of `sebastian/version` dependency + +## [3.1.0] - 2016-01-11 + +### Added + +* Implemented [#234](https://github.com/sebastianbergmann/php-code-coverage/issues/234): Optionally raise an exception when a specified unit of code is not executed + +### Changed + +* The Clover XML report now contains cyclomatic complexity information +* The Clover XML report now contains method visibility information +* Cleanup and refactoring of various areas of code +* Added missing test cases + +### Removed + +* The functionality controlled by the `mapTestClassNameToCoveredClassName` setting has been removed + +[3.1.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.1.0...3.1.1 +[3.1.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.0...3.1.0 + diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-3.2.md b/vendor/phpunit/php-code-coverage/ChangeLog-3.2.md new file mode 100644 index 0000000000..34c65cf49e --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-3.2.md @@ -0,0 +1,23 @@ +# Changes in PHP_CodeCoverage 3.2 + +All notable changes of the PHP_CodeCoverage 3.2 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [3.2.1] - 2016-02-18 + +### Changed + +* Updated dependency information in `composer.json` + +## [3.2.0] - 2016-02-13 + +### Added + +* Added optional check for missing `@covers` annotation when the usage of `@covers` annotations is forced + +### Changed + +* Improved `PHP_CodeCoverage_UnintentionallyCoveredCodeException` message + +[3.2.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.2.0...3.2.1 +[3.2.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.1...3.2.0 + diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-3.3.md b/vendor/phpunit/php-code-coverage/ChangeLog-3.3.md new file mode 100644 index 0000000000..2cf15229e8 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-3.3.md @@ -0,0 +1,33 @@ +# Changes in PHP_CodeCoverage 3.3 + +All notable changes of the PHP_CodeCoverage 3.3 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [3.3.3] - 2016-MM-DD + +### Fixed + +* Fixed [#438](https://github.com/sebastianbergmann/php-code-coverage/issues/438): Wrong base directory for Clover reports + +## [3.3.2] - 2016-05-25 + +### Changed + +* The constructor of `PHP_CodeCoverage_Report_Text` now has default values for its parameters + +## [3.3.1] - 2016-04-08 + +### Fixed + +* Fixed handling of lines that contain `declare` statements + +## [3.3.0] - 2016-03-03 + +### Added + +* Added support for whitelisting classes for the unintentionally covered code unit check + +[3.3.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.3.2...3.3.3 +[3.3.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.3.1...3.3.2 +[3.3.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.3.0...3.3.1 +[3.3.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.2...3.3.0 + diff --git a/vendor/phpunit/php-code-coverage/ChangeLog-4.0.md b/vendor/phpunit/php-code-coverage/ChangeLog-4.0.md new file mode 100644 index 0000000000..30df010275 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/ChangeLog-4.0.md @@ -0,0 +1,67 @@ +# Changes in PHP_CodeCoverage 4.0 + +All notable changes of the PHP_CodeCoverage 4.0 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. + +## [4.0.8] - 2017-04-02 + +* Fixed [#515](https://github.com/sebastianbergmann/php-code-coverage/pull/515): Wrong use of recursive iterator causing duplicate entries in XML coverage report + +## [4.0.7] - 2017-03-01 + +### Changed + +* Cleaned up requirements in `composer.json` + +## [4.0.6] - 2017-02-23 + +### Changed + +* Added support for `phpunit/php-token-stream` 2.0 +* Updated HTML report assets + +## [4.0.5] - 2017-01-20 + +### Fixed + +* Fixed formatting of executed lines percentage for classes in file view + +## [4.0.4] - 2016-12-20 + +### Changed + +* Implemented [#432](https://github.com/sebastianbergmann/php-code-coverage/issues/432): Change how files with no executable lines are displayed in the HTML report + +## [4.0.3] - 2016-11-28 + +### Changed + +* The check for unintentionally covered code is no longer performed for `@medium` and `@large` tests + +## [4.0.2] - 2016-11-01 + +### Fixed + +* Fixed [#440](https://github.com/sebastianbergmann/php-code-coverage/pull/440): Dashboard charts not showing tooltips for data points + +## [4.0.1] - 2016-07-26 + +### Fixed + +* Fixed [#458](https://github.com/sebastianbergmann/php-code-coverage/pull/458): XML report does not know about warning status + +## [4.0.0] - 2016-06-03 + +### Changed + +* This component now uses namespaces + +[4.0.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.7...4.0.8 +[4.0.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.6...4.0.7 +[4.0.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.5...4.0.6 +[4.0.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.4...4.0.5 +[4.0.4]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.3...4.0.4 +[4.0.3]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.2...4.0.3 +[4.0.2]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/php-code-coverage/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/php-code-coverage/compare/3.3...4.0.0 + diff --git a/vendor/phpunit/php-code-coverage/LICENSE b/vendor/phpunit/php-code-coverage/LICENSE new file mode 100644 index 0000000000..fcfa37e804 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/LICENSE @@ -0,0 +1,33 @@ +PHP_CodeCoverage + +Copyright (c) 2009-2015, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/phpunit/php-code-coverage/README.md b/vendor/phpunit/php-code-coverage/README.md new file mode 100644 index 0000000000..c01384b8b8 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/README.md @@ -0,0 +1,51 @@ +[![Latest Stable Version](https://poser.pugx.org/phpunit/php-code-coverage/v/stable.png)](https://packagist.org/packages/phpunit/php-code-coverage) +[![Build Status](https://travis-ci.org/sebastianbergmann/php-code-coverage.svg?branch=master)](https://travis-ci.org/sebastianbergmann/php-code-coverage) + +# PHP_CodeCoverage + +**PHP_CodeCoverage** is a library that provides collection, processing, and rendering functionality for PHP code coverage information. + +## Requirements + +PHP 5.6 is required but using the latest version of PHP is highly recommended. + +### PHP 5 + +[Xdebug](http://xdebug.org/) is the only source of raw code coverage data supported for PHP 5. Version 2.2.1 of Xdebug is required but using the latest version is highly recommended. + +### PHP 7 + +Version 2.4.0 (or later) of [Xdebug](http://xdebug.org/) as well as [phpdbg](http://phpdbg.com/docs) are supported sources of raw code coverage data for PHP 7. + +### HHVM + +A version of HHVM that implements the Xdebug API for code coverage (`xdebug_*_code_coverage()`) is required. + +## Installation + +You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): + + composer require phpunit/php-code-coverage + +If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: + + composer require --dev phpunit/php-code-coverage + +## Using the PHP_CodeCoverage API + +```php +start(''); + +// ... + +$coverage->stop(); + +$writer = new \SebastianBergmann\CodeCoverage\Report\Clover; +$writer->process($coverage, '/tmp/clover.xml'); + +$writer = new \SebastianBergmann\CodeCoverage\Report\Html\Facade; +$writer->process($coverage, '/tmp/code-coverage-report'); +``` + diff --git a/vendor/phpunit/php-code-coverage/build.xml b/vendor/phpunit/php-code-coverage/build.xml new file mode 100644 index 0000000000..d8168c2d85 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/build.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/phpunit/php-code-coverage/composer.json b/vendor/phpunit/php-code-coverage/composer.json new file mode 100644 index 0000000000..7ca434b29e --- /dev/null +++ b/vendor/phpunit/php-code-coverage/composer.json @@ -0,0 +1,51 @@ +{ + "name": "phpunit/php-code-coverage", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "type": "library", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "irc": "irc://irc.freenode.net/phpunit" + }, + "require": { + "php": "^5.6 || ^7.0", + "ext-dom": "*", + "ext-xmlwriter": "*", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "phpunit/php-text-template": "^1.2", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "ext-xdebug": "^2.1.4" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + } +} diff --git a/vendor/phpunit/php-code-coverage/phpunit.xml b/vendor/phpunit/php-code-coverage/phpunit.xml new file mode 100644 index 0000000000..55822f0cf2 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/phpunit.xml @@ -0,0 +1,21 @@ + + + + tests/tests + + + + + src + + + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/CodeCoverage.php b/vendor/phpunit/php-code-coverage/src/CodeCoverage.php new file mode 100644 index 0000000000..35dab3d61b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/CodeCoverage.php @@ -0,0 +1,1107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +use SebastianBergmann\CodeCoverage\Driver\Driver; +use SebastianBergmann\CodeCoverage\Driver\Xdebug; +use SebastianBergmann\CodeCoverage\Driver\HHVM; +use SebastianBergmann\CodeCoverage\Driver\PHPDBG; +use SebastianBergmann\CodeCoverage\Node\Builder; +use SebastianBergmann\CodeCoverage\Node\Directory; +use SebastianBergmann\CodeUnitReverseLookup\Wizard; +use SebastianBergmann\Environment\Runtime; + +/** + * Provides collection functionality for PHP code coverage information. + */ +class CodeCoverage +{ + /** + * @var Driver + */ + private $driver; + + /** + * @var Filter + */ + private $filter; + + /** + * @var Wizard + */ + private $wizard; + + /** + * @var bool + */ + private $cacheTokens = false; + + /** + * @var bool + */ + private $checkForUnintentionallyCoveredCode = false; + + /** + * @var bool + */ + private $forceCoversAnnotation = false; + + /** + * @var bool + */ + private $checkForUnexecutedCoveredCode = false; + + /** + * @var bool + */ + private $checkForMissingCoversAnnotation = false; + + /** + * @var bool + */ + private $addUncoveredFilesFromWhitelist = true; + + /** + * @var bool + */ + private $processUncoveredFilesFromWhitelist = false; + + /** + * @var bool + */ + private $ignoreDeprecatedCode = false; + + /** + * @var mixed + */ + private $currentId; + + /** + * Code coverage data. + * + * @var array + */ + private $data = []; + + /** + * @var array + */ + private $ignoredLines = []; + + /** + * @var bool + */ + private $disableIgnoredLines = false; + + /** + * Test data. + * + * @var array + */ + private $tests = []; + + /** + * @var string[] + */ + private $unintentionallyCoveredSubclassesWhitelist = []; + + /** + * Determine if the data has been initialized or not + * + * @var bool + */ + private $isInitialized = false; + + /** + * Determine whether we need to check for dead and unused code on each test + * + * @var bool + */ + private $shouldCheckForDeadAndUnused = true; + + /** + * Constructor. + * + * @param Driver $driver + * @param Filter $filter + * + * @throws RuntimeException + */ + public function __construct(Driver $driver = null, Filter $filter = null) + { + if ($driver === null) { + $driver = $this->selectDriver(); + } + + if ($filter === null) { + $filter = new Filter; + } + + $this->driver = $driver; + $this->filter = $filter; + + $this->wizard = new Wizard; + } + + /** + * Returns the code coverage information as a graph of node objects. + * + * @return Directory + */ + public function getReport() + { + $builder = new Builder; + + return $builder->build($this); + } + + /** + * Clears collected code coverage data. + */ + public function clear() + { + $this->isInitialized = false; + $this->currentId = null; + $this->data = []; + $this->tests = []; + } + + /** + * Returns the filter object used. + * + * @return Filter + */ + public function filter() + { + return $this->filter; + } + + /** + * Returns the collected code coverage data. + * Set $raw = true to bypass all filters. + * + * @param bool $raw + * + * @return array + */ + public function getData($raw = false) + { + if (!$raw && $this->addUncoveredFilesFromWhitelist) { + $this->addUncoveredFilesFromWhitelist(); + } + + return $this->data; + } + + /** + * Sets the coverage data. + * + * @param array $data + */ + public function setData(array $data) + { + $this->data = $data; + } + + /** + * Returns the test data. + * + * @return array + */ + public function getTests() + { + return $this->tests; + } + + /** + * Sets the test data. + * + * @param array $tests + */ + public function setTests(array $tests) + { + $this->tests = $tests; + } + + /** + * Start collection of code coverage information. + * + * @param mixed $id + * @param bool $clear + * + * @throws InvalidArgumentException + */ + public function start($id, $clear = false) + { + if (!is_bool($clear)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + if ($clear) { + $this->clear(); + } + + if ($this->isInitialized === false) { + $this->initializeData(); + } + + $this->currentId = $id; + + $this->driver->start($this->shouldCheckForDeadAndUnused); + } + + /** + * Stop collection of code coverage information. + * + * @param bool $append + * @param mixed $linesToBeCovered + * @param array $linesToBeUsed + * + * @return array + * + * @throws InvalidArgumentException + */ + public function stop($append = true, $linesToBeCovered = [], array $linesToBeUsed = []) + { + if (!is_bool($append)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) { + throw InvalidArgumentException::create( + 2, + 'array or false' + ); + } + + $data = $this->driver->stop(); + $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed); + + $this->currentId = null; + + return $data; + } + + /** + * Appends code coverage data. + * + * @param array $data + * @param mixed $id + * @param bool $append + * @param mixed $linesToBeCovered + * @param array $linesToBeUsed + * + * @throws RuntimeException + */ + public function append(array $data, $id = null, $append = true, $linesToBeCovered = [], array $linesToBeUsed = []) + { + if ($id === null) { + $id = $this->currentId; + } + + if ($id === null) { + throw new RuntimeException; + } + + $this->applyListsFilter($data); + $this->applyIgnoredLinesFilter($data); + $this->initializeFilesThatAreSeenTheFirstTime($data); + + if (!$append) { + return; + } + + if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') { + $this->applyCoversAnnotationFilter( + $data, + $linesToBeCovered, + $linesToBeUsed + ); + } + + if (empty($data)) { + return; + } + + $size = 'unknown'; + $status = null; + + if ($id instanceof \PHPUnit_Framework_TestCase) { + $_size = $id->getSize(); + + if ($_size == \PHPUnit_Util_Test::SMALL) { + $size = 'small'; + } elseif ($_size == \PHPUnit_Util_Test::MEDIUM) { + $size = 'medium'; + } elseif ($_size == \PHPUnit_Util_Test::LARGE) { + $size = 'large'; + } + + $status = $id->getStatus(); + $id = get_class($id) . '::' . $id->getName(); + } elseif ($id instanceof \PHPUnit_Extensions_PhptTestCase) { + $size = 'large'; + $id = $id->getName(); + } + + $this->tests[$id] = ['size' => $size, 'status' => $status]; + + foreach ($data as $file => $lines) { + if (!$this->filter->isFile($file)) { + continue; + } + + foreach ($lines as $k => $v) { + if ($v == Driver::LINE_EXECUTED) { + if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) { + $this->data[$file][$k][] = $id; + } + } + } + } + } + + /** + * Merges the data from another instance. + * + * @param CodeCoverage $that + */ + public function merge(CodeCoverage $that) + { + $this->filter->setWhitelistedFiles( + array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) + ); + + foreach ($that->data as $file => $lines) { + if (!isset($this->data[$file])) { + if (!$this->filter->isFiltered($file)) { + $this->data[$file] = $lines; + } + + continue; + } + + foreach ($lines as $line => $data) { + if ($data !== null) { + if (!isset($this->data[$file][$line])) { + $this->data[$file][$line] = $data; + } else { + $this->data[$file][$line] = array_unique( + array_merge($this->data[$file][$line], $data) + ); + } + } + } + } + + $this->tests = array_merge($this->tests, $that->getTests()); + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setCacheTokens($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->cacheTokens = $flag; + } + + /** + * @return bool + */ + public function getCacheTokens() + { + return $this->cacheTokens; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setCheckForUnintentionallyCoveredCode($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->checkForUnintentionallyCoveredCode = $flag; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setForceCoversAnnotation($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->forceCoversAnnotation = $flag; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setCheckForMissingCoversAnnotation($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->checkForMissingCoversAnnotation = $flag; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setCheckForUnexecutedCoveredCode($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->checkForUnexecutedCoveredCode = $flag; + } + + /** + * @deprecated + * + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setMapTestClassNameToCoveredClassName($flag) + { + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setAddUncoveredFilesFromWhitelist($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->addUncoveredFilesFromWhitelist = $flag; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setProcessUncoveredFilesFromWhitelist($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->processUncoveredFilesFromWhitelist = $flag; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setDisableIgnoredLines($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->disableIgnoredLines = $flag; + } + + /** + * @param bool $flag + * + * @throws InvalidArgumentException + */ + public function setIgnoreDeprecatedCode($flag) + { + if (!is_bool($flag)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + $this->ignoreDeprecatedCode = $flag; + } + + /** + * @param array $whitelist + */ + public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist) + { + $this->unintentionallyCoveredSubclassesWhitelist = $whitelist; + } + + /** + * Applies the @covers annotation filtering. + * + * @param array $data + * @param mixed $linesToBeCovered + * @param array $linesToBeUsed + * + * @throws MissingCoversAnnotationException + * @throws UnintentionallyCoveredCodeException + */ + private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed) + { + if ($linesToBeCovered === false || + ($this->forceCoversAnnotation && empty($linesToBeCovered))) { + if ($this->checkForMissingCoversAnnotation) { + throw new MissingCoversAnnotationException; + } + + $data = []; + + return; + } + + if (empty($linesToBeCovered)) { + return; + } + + if ($this->checkForUnintentionallyCoveredCode && + (!$this->currentId instanceof \PHPUnit_Framework_TestCase || + (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { + $this->performUnintentionallyCoveredCodeCheck( + $data, + $linesToBeCovered, + $linesToBeUsed + ); + } + + if ($this->checkForUnexecutedCoveredCode) { + $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed); + } + + $data = array_intersect_key($data, $linesToBeCovered); + + foreach (array_keys($data) as $filename) { + $_linesToBeCovered = array_flip($linesToBeCovered[$filename]); + + $data[$filename] = array_intersect_key( + $data[$filename], + $_linesToBeCovered + ); + } + } + + /** + * Applies the whitelist filtering. + * + * @param array $data + */ + private function applyListsFilter(array &$data) + { + foreach (array_keys($data) as $filename) { + if ($this->filter->isFiltered($filename)) { + unset($data[$filename]); + } + } + } + + /** + * Applies the "ignored lines" filtering. + * + * @param array $data + */ + private function applyIgnoredLinesFilter(array &$data) + { + foreach (array_keys($data) as $filename) { + if (!$this->filter->isFile($filename)) { + continue; + } + + foreach ($this->getLinesToBeIgnored($filename) as $line) { + unset($data[$filename][$line]); + } + } + } + + /** + * @param array $data + */ + private function initializeFilesThatAreSeenTheFirstTime(array $data) + { + foreach ($data as $file => $lines) { + if ($this->filter->isFile($file) && !isset($this->data[$file])) { + $this->data[$file] = []; + + foreach ($lines as $k => $v) { + $this->data[$file][$k] = $v == -2 ? null : []; + } + } + } + } + + /** + * Processes whitelisted files that are not covered. + */ + private function addUncoveredFilesFromWhitelist() + { + $data = []; + $uncoveredFiles = array_diff( + $this->filter->getWhitelist(), + array_keys($this->data) + ); + + foreach ($uncoveredFiles as $uncoveredFile) { + if (!file_exists($uncoveredFile)) { + continue; + } + + if (!$this->processUncoveredFilesFromWhitelist) { + $data[$uncoveredFile] = []; + + $lines = count(file($uncoveredFile)); + + for ($i = 1; $i <= $lines; $i++) { + $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED; + } + } + } + + $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); + } + + /** + * Returns the lines of a source file that should be ignored. + * + * @param string $filename + * + * @return array + * + * @throws InvalidArgumentException + */ + private function getLinesToBeIgnored($filename) + { + if (!is_string($filename)) { + throw InvalidArgumentException::create( + 1, + 'string' + ); + } + + if (!isset($this->ignoredLines[$filename])) { + $this->ignoredLines[$filename] = []; + + if ($this->disableIgnoredLines) { + return $this->ignoredLines[$filename]; + } + + $ignore = false; + $stop = false; + $lines = file($filename); + $numLines = count($lines); + + foreach ($lines as $index => $line) { + if (!trim($line)) { + $this->ignoredLines[$filename][] = $index + 1; + } + } + + if ($this->cacheTokens) { + $tokens = \PHP_Token_Stream_CachingFactory::get($filename); + } else { + $tokens = new \PHP_Token_Stream($filename); + } + + $classes = array_merge($tokens->getClasses(), $tokens->getTraits()); + $tokens = $tokens->tokens(); + + foreach ($tokens as $token) { + switch (get_class($token)) { + case 'PHP_Token_COMMENT': + case 'PHP_Token_DOC_COMMENT': + $_token = trim($token); + $_line = trim($lines[$token->getLine() - 1]); + + if ($_token == '// @codeCoverageIgnore' || + $_token == '//@codeCoverageIgnore') { + $ignore = true; + $stop = true; + } elseif ($_token == '// @codeCoverageIgnoreStart' || + $_token == '//@codeCoverageIgnoreStart') { + $ignore = true; + } elseif ($_token == '// @codeCoverageIgnoreEnd' || + $_token == '//@codeCoverageIgnoreEnd') { + $stop = true; + } + + if (!$ignore) { + $start = $token->getLine(); + $end = $start + substr_count($token, "\n"); + + // Do not ignore the first line when there is a token + // before the comment + if (0 !== strpos($_token, $_line)) { + $start++; + } + + for ($i = $start; $i < $end; $i++) { + $this->ignoredLines[$filename][] = $i; + } + + // A DOC_COMMENT token or a COMMENT token starting with "/*" + // does not contain the final \n character in its text + if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) { + $this->ignoredLines[$filename][] = $i; + } + } + break; + + case 'PHP_Token_INTERFACE': + case 'PHP_Token_TRAIT': + case 'PHP_Token_CLASS': + case 'PHP_Token_FUNCTION': + /* @var \PHP_Token_Interface $token */ + + $docblock = $token->getDocblock(); + + $this->ignoredLines[$filename][] = $token->getLine(); + + if (strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && strpos($docblock, '@deprecated'))) { + $endLine = $token->getEndLine(); + + for ($i = $token->getLine(); $i <= $endLine; $i++) { + $this->ignoredLines[$filename][] = $i; + } + } elseif ($token instanceof \PHP_Token_INTERFACE || + $token instanceof \PHP_Token_TRAIT || + $token instanceof \PHP_Token_CLASS) { + if (empty($classes[$token->getName()]['methods'])) { + for ($i = $token->getLine(); + $i <= $token->getEndLine(); + $i++) { + $this->ignoredLines[$filename][] = $i; + } + } else { + $firstMethod = array_shift( + $classes[$token->getName()]['methods'] + ); + + do { + $lastMethod = array_pop( + $classes[$token->getName()]['methods'] + ); + } while ($lastMethod !== null && + substr($lastMethod['signature'], 0, 18) == 'anonymous function'); + + if ($lastMethod === null) { + $lastMethod = $firstMethod; + } + + for ($i = $token->getLine(); + $i < $firstMethod['startLine']; + $i++) { + $this->ignoredLines[$filename][] = $i; + } + + for ($i = $token->getEndLine(); + $i > $lastMethod['endLine']; + $i--) { + $this->ignoredLines[$filename][] = $i; + } + } + } + break; + + case 'PHP_Token_NAMESPACE': + $this->ignoredLines[$filename][] = $token->getEndLine(); + + // Intentional fallthrough + case 'PHP_Token_DECLARE': + case 'PHP_Token_OPEN_TAG': + case 'PHP_Token_CLOSE_TAG': + case 'PHP_Token_USE': + $this->ignoredLines[$filename][] = $token->getLine(); + break; + } + + if ($ignore) { + $this->ignoredLines[$filename][] = $token->getLine(); + + if ($stop) { + $ignore = false; + $stop = false; + } + } + } + + $this->ignoredLines[$filename][] = $numLines + 1; + + $this->ignoredLines[$filename] = array_unique( + $this->ignoredLines[$filename] + ); + + sort($this->ignoredLines[$filename]); + } + + return $this->ignoredLines[$filename]; + } + + /** + * @param array $data + * @param array $linesToBeCovered + * @param array $linesToBeUsed + * + * @throws UnintentionallyCoveredCodeException + */ + private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed) + { + $allowedLines = $this->getAllowedLines( + $linesToBeCovered, + $linesToBeUsed + ); + + $unintentionallyCoveredUnits = []; + + foreach ($data as $file => $_data) { + foreach ($_data as $line => $flag) { + if ($flag == 1 && !isset($allowedLines[$file][$line])) { + $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); + } + } + } + + $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits); + + if (!empty($unintentionallyCoveredUnits)) { + throw new UnintentionallyCoveredCodeException( + $unintentionallyCoveredUnits + ); + } + } + + /** + * @param array $data + * @param array $linesToBeCovered + * @param array $linesToBeUsed + * + * @throws CoveredCodeNotExecutedException + */ + private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed) + { + $expectedLines = $this->getAllowedLines( + $linesToBeCovered, + $linesToBeUsed + ); + + foreach ($data as $file => $_data) { + foreach (array_keys($_data) as $line) { + if (!isset($expectedLines[$file][$line])) { + continue; + } + + unset($expectedLines[$file][$line]); + } + } + + $message = ''; + + foreach ($expectedLines as $file => $lines) { + if (empty($lines)) { + continue; + } + + foreach (array_keys($lines) as $line) { + $message .= sprintf('- %s:%d' . PHP_EOL, $file, $line); + } + } + + if (!empty($message)) { + throw new CoveredCodeNotExecutedException($message); + } + } + + /** + * @param array $linesToBeCovered + * @param array $linesToBeUsed + * + * @return array + */ + private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) + { + $allowedLines = []; + + foreach (array_keys($linesToBeCovered) as $file) { + if (!isset($allowedLines[$file])) { + $allowedLines[$file] = []; + } + + $allowedLines[$file] = array_merge( + $allowedLines[$file], + $linesToBeCovered[$file] + ); + } + + foreach (array_keys($linesToBeUsed) as $file) { + if (!isset($allowedLines[$file])) { + $allowedLines[$file] = []; + } + + $allowedLines[$file] = array_merge( + $allowedLines[$file], + $linesToBeUsed[$file] + ); + } + + foreach (array_keys($allowedLines) as $file) { + $allowedLines[$file] = array_flip( + array_unique($allowedLines[$file]) + ); + } + + return $allowedLines; + } + + /** + * @return Driver + * + * @throws RuntimeException + */ + private function selectDriver() + { + $runtime = new Runtime; + + if (!$runtime->canCollectCodeCoverage()) { + throw new RuntimeException('No code coverage driver available'); + } + + if ($runtime->isHHVM()) { + return new HHVM; + } elseif ($runtime->isPHPDBG()) { + return new PHPDBG; + } else { + return new Xdebug; + } + } + + /** + * @param array $unintentionallyCoveredUnits + * + * @return array + */ + private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits) + { + $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits); + sort($unintentionallyCoveredUnits); + + foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) { + $unit = explode('::', $unintentionallyCoveredUnits[$k]); + + if (count($unit) != 2) { + continue; + } + + $class = new \ReflectionClass($unit[0]); + + foreach ($this->unintentionallyCoveredSubclassesWhitelist as $whitelisted) { + if ($class->isSubclassOf($whitelisted)) { + unset($unintentionallyCoveredUnits[$k]); + break; + } + } + } + + return array_values($unintentionallyCoveredUnits); + } + + /** + * If we are processing uncovered files from whitelist, + * we can initialize the data before we start to speed up the tests + */ + protected function initializeData() + { + $this->isInitialized = true; + + if ($this->processUncoveredFilesFromWhitelist) { + $this->shouldCheckForDeadAndUnused = false; + + $this->driver->start(true); + + foreach ($this->filter->getWhitelist() as $file) { + if ($this->filter->isFile($file)) { + include_once($file); + } + } + + $data = []; + $coverage = $this->driver->stop(); + + foreach ($coverage as $file => $fileCoverage) { + if ($this->filter->isFiltered($file)) { + continue; + } + + foreach (array_keys($fileCoverage) as $key) { + if ($fileCoverage[$key] == Driver::LINE_EXECUTED) { + $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED; + } + } + + $data[$file] = $fileCoverage; + } + + $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); + } + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/Driver.php b/vendor/phpunit/php-code-coverage/src/Driver/Driver.php new file mode 100644 index 0000000000..bdd1b9794b --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/Driver.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Driver; + +/** + * Interface for code coverage drivers. + */ +interface Driver +{ + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + const LINE_EXECUTED = 1; + + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + const LINE_NOT_EXECUTED = -1; + + /** + * @var int + * + * @see http://xdebug.org/docs/code_coverage + */ + const LINE_NOT_EXECUTABLE = -2; + + /** + * Start collection of code coverage information. + * + * @param bool $determineUnusedAndDead + */ + public function start($determineUnusedAndDead = true); + + /** + * Stop collection of code coverage information. + * + * @return array + */ + public function stop(); +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/HHVM.php b/vendor/phpunit/php-code-coverage/src/Driver/HHVM.php new file mode 100644 index 0000000000..b35ea81bbc --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/HHVM.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Driver; + +/** + * Driver for HHVM's code coverage functionality. + * + * @codeCoverageIgnore + */ +class HHVM extends Xdebug +{ + /** + * Start collection of code coverage information. + * + * @param bool $determineUnusedAndDead + */ + public function start($determineUnusedAndDead = true) + { + xdebug_start_code_coverage(); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/PHPDBG.php b/vendor/phpunit/php-code-coverage/src/Driver/PHPDBG.php new file mode 100644 index 0000000000..86cc8444c2 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/PHPDBG.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Driver; + +use SebastianBergmann\CodeCoverage\RuntimeException; + +/** + * Driver for PHPDBG's code coverage functionality. + * + * @codeCoverageIgnore + */ +class PHPDBG implements Driver +{ + /** + * Constructor. + */ + public function __construct() + { + if (PHP_SAPI !== 'phpdbg') { + throw new RuntimeException( + 'This driver requires the PHPDBG SAPI' + ); + } + + if (!function_exists('phpdbg_start_oplog')) { + throw new RuntimeException( + 'This build of PHPDBG does not support code coverage' + ); + } + } + + /** + * Start collection of code coverage information. + * + * @param bool $determineUnusedAndDead + */ + public function start($determineUnusedAndDead = true) + { + phpdbg_start_oplog(); + } + + /** + * Stop collection of code coverage information. + * + * @return array + */ + public function stop() + { + static $fetchedLines = []; + + $dbgData = phpdbg_end_oplog(); + + if ($fetchedLines == []) { + $sourceLines = phpdbg_get_executable(); + } else { + $newFiles = array_diff( + get_included_files(), + array_keys($fetchedLines) + ); + + if ($newFiles) { + $sourceLines = phpdbg_get_executable( + ['files' => $newFiles] + ); + } else { + $sourceLines = []; + } + } + + foreach ($sourceLines as $file => $lines) { + foreach ($lines as $lineNo => $numExecuted) { + $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED; + } + } + + $fetchedLines = array_merge($fetchedLines, $sourceLines); + + return $this->detectExecutedLines($fetchedLines, $dbgData); + } + + /** + * Convert phpdbg based data into the format CodeCoverage expects + * + * @param array $sourceLines + * @param array $dbgData + * + * @return array + */ + private function detectExecutedLines(array $sourceLines, array $dbgData) + { + foreach ($dbgData as $file => $coveredLines) { + foreach ($coveredLines as $lineNo => $numExecuted) { + // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown. + // make sure we only mark lines executed which are actually executable. + if (isset($sourceLines[$file][$lineNo])) { + $sourceLines[$file][$lineNo] = self::LINE_EXECUTED; + } + } + } + + return $sourceLines; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Driver/Xdebug.php b/vendor/phpunit/php-code-coverage/src/Driver/Xdebug.php new file mode 100644 index 0000000000..30099e0570 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Driver/Xdebug.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Driver; + +use SebastianBergmann\CodeCoverage\RuntimeException; + +/** + * Driver for Xdebug's code coverage functionality. + * + * @codeCoverageIgnore + */ +class Xdebug implements Driver +{ + /** + * Cache the number of lines for each file + * + * @var array + */ + private $cacheNumLines = []; + + /** + * Constructor. + */ + public function __construct() + { + if (!extension_loaded('xdebug')) { + throw new RuntimeException('This driver requires Xdebug'); + } + + if (version_compare(phpversion('xdebug'), '2.2.1', '>=') && + !ini_get('xdebug.coverage_enable')) { + throw new RuntimeException( + 'xdebug.coverage_enable=On has to be set in php.ini' + ); + } + } + + /** + * Start collection of code coverage information. + * + * @param bool $determineUnusedAndDead + */ + public function start($determineUnusedAndDead = true) + { + if ($determineUnusedAndDead) { + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + } else { + xdebug_start_code_coverage(); + } + } + + /** + * Stop collection of code coverage information. + * + * @return array + */ + public function stop() + { + $data = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + + return $this->cleanup($data); + } + + /** + * @param array $data + * + * @return array + */ + private function cleanup(array $data) + { + foreach (array_keys($data) as $file) { + unset($data[$file][0]); + + if (strpos($file, 'xdebug://debug-eval') !== 0 && file_exists($file)) { + $numLines = $this->getNumberOfLinesInFile($file); + + foreach (array_keys($data[$file]) as $line) { + if ($line > $numLines) { + unset($data[$file][$line]); + } + } + } + } + + return $data; + } + + /** + * @param string $file + * + * @return int + */ + private function getNumberOfLinesInFile($file) + { + if (!isset($this->cacheNumLines[$file])) { + $buffer = file_get_contents($file); + $lines = substr_count($buffer, "\n"); + + if (substr($buffer, -1) !== "\n") { + $lines++; + } + + $this->cacheNumLines[$file] = $lines; + } + + return $this->cacheNumLines[$file]; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php b/vendor/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php new file mode 100644 index 0000000000..ca28a231b1 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +/** + * Exception that is raised when covered code is not executed. + */ +class CoveredCodeNotExecutedException extends RuntimeException +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/Exception.php b/vendor/phpunit/php-code-coverage/src/Exception/Exception.php new file mode 100644 index 0000000000..a3ba4c4c6d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/Exception.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +/** + * Exception interface for php-code-coverage component. + */ +interface Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php b/vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..1733f6cb5a --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ + /** + * @param int $argument + * @param string $type + * @param mixed $value + * + * @return InvalidArgumentException + */ + public static function create($argument, $type, $value = null) + { + $stack = debug_backtrace(0); + + return new self( + sprintf( + 'Argument #%d%sof %s::%s() must be a %s', + $argument, + $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ', + $stack[1]['class'], + $stack[1]['function'], + $type + ) + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php b/vendor/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php new file mode 100644 index 0000000000..7bc5cf3e8f --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +/** + * Exception that is raised when @covers must be used but is not. + */ +class MissingCoversAnnotationException extends RuntimeException +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/RuntimeException.php b/vendor/phpunit/php-code-coverage/src/Exception/RuntimeException.php new file mode 100644 index 0000000000..c143db7da9 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/RuntimeException.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php b/vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php new file mode 100644 index 0000000000..3ea542b1fd --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +/** + * Exception that is raised when code is unintentionally covered. + */ +class UnintentionallyCoveredCodeException extends RuntimeException +{ + /** + * @var array + */ + private $unintentionallyCoveredUnits = []; + + /** + * @param array $unintentionallyCoveredUnits + */ + public function __construct(array $unintentionallyCoveredUnits) + { + $this->unintentionallyCoveredUnits = $unintentionallyCoveredUnits; + + parent::__construct($this->toString()); + } + + /** + * @return array + */ + public function getUnintentionallyCoveredUnits() + { + return $this->unintentionallyCoveredUnits; + } + + /** + * @return string + */ + private function toString() + { + $message = ''; + + foreach ($this->unintentionallyCoveredUnits as $unit) { + $message .= '- ' . $unit . "\n"; + } + + return $message; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Filter.php b/vendor/phpunit/php-code-coverage/src/Filter.php new file mode 100644 index 0000000000..771a657ae6 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Filter.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage; + +/** + * Filter for whitelisting of code coverage information. + */ +class Filter +{ + /** + * Source files that are whitelisted. + * + * @var array + */ + private $whitelistedFiles = []; + + /** + * Adds a directory to the whitelist (recursively). + * + * @param string $directory + * @param string $suffix + * @param string $prefix + */ + public function addDirectoryToWhitelist($directory, $suffix = '.php', $prefix = '') + { + $facade = new \File_Iterator_Facade; + $files = $facade->getFilesAsArray($directory, $suffix, $prefix); + + foreach ($files as $file) { + $this->addFileToWhitelist($file); + } + } + + /** + * Adds a file to the whitelist. + * + * @param string $filename + */ + public function addFileToWhitelist($filename) + { + $this->whitelistedFiles[realpath($filename)] = true; + } + + /** + * Adds files to the whitelist. + * + * @param array $files + */ + public function addFilesToWhitelist(array $files) + { + foreach ($files as $file) { + $this->addFileToWhitelist($file); + } + } + + /** + * Removes a directory from the whitelist (recursively). + * + * @param string $directory + * @param string $suffix + * @param string $prefix + */ + public function removeDirectoryFromWhitelist($directory, $suffix = '.php', $prefix = '') + { + $facade = new \File_Iterator_Facade; + $files = $facade->getFilesAsArray($directory, $suffix, $prefix); + + foreach ($files as $file) { + $this->removeFileFromWhitelist($file); + } + } + + /** + * Removes a file from the whitelist. + * + * @param string $filename + */ + public function removeFileFromWhitelist($filename) + { + $filename = realpath($filename); + + unset($this->whitelistedFiles[$filename]); + } + + /** + * Checks whether a filename is a real filename. + * + * @param string $filename + * + * @return bool + */ + public function isFile($filename) + { + if ($filename == '-' || + strpos($filename, 'vfs://') === 0 || + strpos($filename, 'xdebug://debug-eval') !== false || + strpos($filename, 'eval()\'d code') !== false || + strpos($filename, 'runtime-created function') !== false || + strpos($filename, 'runkit created function') !== false || + strpos($filename, 'assert code') !== false || + strpos($filename, 'regexp code') !== false) { + return false; + } + + return file_exists($filename); + } + + /** + * Checks whether or not a file is filtered. + * + * @param string $filename + * + * @return bool + */ + public function isFiltered($filename) + { + if (!$this->isFile($filename)) { + return true; + } + + $filename = realpath($filename); + + return !isset($this->whitelistedFiles[$filename]); + } + + /** + * Returns the list of whitelisted files. + * + * @return array + */ + public function getWhitelist() + { + return array_keys($this->whitelistedFiles); + } + + /** + * Returns whether this filter has a whitelist. + * + * @return bool + */ + public function hasWhitelist() + { + return !empty($this->whitelistedFiles); + } + + /** + * Returns the whitelisted files. + * + * @return array + */ + public function getWhitelistedFiles() + { + return $this->whitelistedFiles; + } + + /** + * Sets the whitelisted files. + * + * @param array $whitelistedFiles + */ + public function setWhitelistedFiles($whitelistedFiles) + { + $this->whitelistedFiles = $whitelistedFiles; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php b/vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php new file mode 100644 index 0000000000..f3608058ed --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/AbstractNode.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Node; + +use SebastianBergmann\CodeCoverage\Util; + +/** + * Base class for nodes in the code coverage information tree. + */ +abstract class AbstractNode implements \Countable +{ + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $path; + + /** + * @var array + */ + private $pathArray; + + /** + * @var AbstractNode + */ + private $parent; + + /** + * @var string + */ + private $id; + + /** + * Constructor. + * + * @param string $name + * @param AbstractNode $parent + */ + public function __construct($name, AbstractNode $parent = null) + { + if (substr($name, -1) == '/') { + $name = substr($name, 0, -1); + } + + $this->name = $name; + $this->parent = $parent; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return string + */ + public function getId() + { + if ($this->id === null) { + $parent = $this->getParent(); + + if ($parent === null) { + $this->id = 'index'; + } else { + $parentId = $parent->getId(); + + if ($parentId == 'index') { + $this->id = str_replace(':', '_', $this->name); + } else { + $this->id = $parentId . '/' . $this->name; + } + } + } + + return $this->id; + } + + /** + * @return string + */ + public function getPath() + { + if ($this->path === null) { + if ($this->parent === null || $this->parent->getPath() === null || $this->parent->getPath() === false) { + $this->path = $this->name; + } else { + $this->path = $this->parent->getPath() . '/' . $this->name; + } + } + + return $this->path; + } + + /** + * @return array + */ + public function getPathAsArray() + { + if ($this->pathArray === null) { + if ($this->parent === null) { + $this->pathArray = []; + } else { + $this->pathArray = $this->parent->getPathAsArray(); + } + + $this->pathArray[] = $this; + } + + return $this->pathArray; + } + + /** + * @return AbstractNode + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns the percentage of classes that has been tested. + * + * @param bool $asString + * + * @return int + */ + public function getTestedClassesPercent($asString = true) + { + return Util::percent( + $this->getNumTestedClasses(), + $this->getNumClasses(), + $asString + ); + } + + /** + * Returns the percentage of traits that has been tested. + * + * @param bool $asString + * + * @return int + */ + public function getTestedTraitsPercent($asString = true) + { + return Util::percent( + $this->getNumTestedTraits(), + $this->getNumTraits(), + $asString + ); + } + + /** + * Returns the percentage of traits that has been tested. + * + * @param bool $asString + * + * @return int + */ + public function getTestedClassesAndTraitsPercent($asString = true) + { + return Util::percent( + $this->getNumTestedClassesAndTraits(), + $this->getNumClassesAndTraits(), + $asString + ); + } + + /** + * Returns the percentage of methods that has been tested. + * + * @param bool $asString + * + * @return int + */ + public function getTestedMethodsPercent($asString = true) + { + return Util::percent( + $this->getNumTestedMethods(), + $this->getNumMethods(), + $asString + ); + } + + /** + * Returns the percentage of executed lines. + * + * @param bool $asString + * + * @return int + */ + public function getLineExecutedPercent($asString = true) + { + return Util::percent( + $this->getNumExecutedLines(), + $this->getNumExecutableLines(), + $asString + ); + } + + /** + * Returns the number of classes and traits. + * + * @return int + */ + public function getNumClassesAndTraits() + { + return $this->getNumClasses() + $this->getNumTraits(); + } + + /** + * Returns the number of tested classes and traits. + * + * @return int + */ + public function getNumTestedClassesAndTraits() + { + return $this->getNumTestedClasses() + $this->getNumTestedTraits(); + } + + /** + * Returns the classes and traits of this node. + * + * @return array + */ + public function getClassesAndTraits() + { + return array_merge($this->getClasses(), $this->getTraits()); + } + + /** + * Returns the classes of this node. + * + * @return array + */ + abstract public function getClasses(); + + /** + * Returns the traits of this node. + * + * @return array + */ + abstract public function getTraits(); + + /** + * Returns the functions of this node. + * + * @return array + */ + abstract public function getFunctions(); + + /** + * Returns the LOC/CLOC/NCLOC of this node. + * + * @return array + */ + abstract public function getLinesOfCode(); + + /** + * Returns the number of executable lines. + * + * @return int + */ + abstract public function getNumExecutableLines(); + + /** + * Returns the number of executed lines. + * + * @return int + */ + abstract public function getNumExecutedLines(); + + /** + * Returns the number of classes. + * + * @return int + */ + abstract public function getNumClasses(); + + /** + * Returns the number of tested classes. + * + * @return int + */ + abstract public function getNumTestedClasses(); + + /** + * Returns the number of traits. + * + * @return int + */ + abstract public function getNumTraits(); + + /** + * Returns the number of tested traits. + * + * @return int + */ + abstract public function getNumTestedTraits(); + + /** + * Returns the number of methods. + * + * @return int + */ + abstract public function getNumMethods(); + + /** + * Returns the number of tested methods. + * + * @return int + */ + abstract public function getNumTestedMethods(); + + /** + * Returns the number of functions. + * + * @return int + */ + abstract public function getNumFunctions(); + + /** + * Returns the number of tested functions. + * + * @return int + */ + abstract public function getNumTestedFunctions(); +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/Builder.php b/vendor/phpunit/php-code-coverage/src/Node/Builder.php new file mode 100644 index 0000000000..8a6a65c1b3 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/Builder.php @@ -0,0 +1,244 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Node; + +use SebastianBergmann\CodeCoverage\CodeCoverage; + +class Builder +{ + /** + * @param CodeCoverage $coverage + * + * @return Directory + */ + public function build(CodeCoverage $coverage) + { + $files = $coverage->getData(); + $commonPath = $this->reducePaths($files); + $root = new Directory( + $commonPath, + null + ); + + $this->addItems( + $root, + $this->buildDirectoryStructure($files), + $coverage->getTests(), + $coverage->getCacheTokens() + ); + + return $root; + } + + /** + * @param Directory $root + * @param array $items + * @param array $tests + * @param bool $cacheTokens + */ + private function addItems(Directory $root, array $items, array $tests, $cacheTokens) + { + foreach ($items as $key => $value) { + if (substr($key, -2) == '/f') { + $key = substr($key, 0, -2); + + if (file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) { + $root->addFile($key, $value, $tests, $cacheTokens); + } + } else { + $child = $root->addDirectory($key); + $this->addItems($child, $value, $tests, $cacheTokens); + } + } + } + + /** + * Builds an array representation of the directory structure. + * + * For instance, + * + * + * Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * is transformed into + * + * + * Array + * ( + * [.] => Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * ) + * + * + * @param array $files + * + * @return array + */ + private function buildDirectoryStructure($files) + { + $result = []; + + foreach ($files as $path => $file) { + $path = explode('/', $path); + $pointer = &$result; + $max = count($path); + + for ($i = 0; $i < $max; $i++) { + if ($i == ($max - 1)) { + $type = '/f'; + } else { + $type = ''; + } + + $pointer = &$pointer[$path[$i] . $type]; + } + + $pointer = $file; + } + + return $result; + } + + /** + * Reduces the paths by cutting the longest common start path. + * + * For instance, + * + * + * Array + * ( + * [/home/sb/Money/Money.php] => Array + * ( + * ... + * ) + * + * [/home/sb/Money/MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * is reduced to + * + * + * Array + * ( + * [Money.php] => Array + * ( + * ... + * ) + * + * [MoneyBag.php] => Array + * ( + * ... + * ) + * ) + * + * + * @param array $files + * + * @return string + */ + private function reducePaths(&$files) + { + if (empty($files)) { + return '.'; + } + + $commonPath = ''; + $paths = array_keys($files); + + if (count($files) == 1) { + $commonPath = dirname($paths[0]) . '/'; + $files[basename($paths[0])] = $files[$paths[0]]; + + unset($files[$paths[0]]); + + return $commonPath; + } + + $max = count($paths); + + for ($i = 0; $i < $max; $i++) { + // strip phar:// prefixes + if (strpos($paths[$i], 'phar://') === 0) { + $paths[$i] = substr($paths[$i], 7); + $paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR); + } + $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); + + if (empty($paths[$i][0])) { + $paths[$i][0] = DIRECTORY_SEPARATOR; + } + } + + $done = false; + $max = count($paths); + + while (!$done) { + for ($i = 0; $i < $max - 1; $i++) { + if (!isset($paths[$i][0]) || + !isset($paths[$i+1][0]) || + $paths[$i][0] != $paths[$i+1][0]) { + $done = true; + break; + } + } + + if (!$done) { + $commonPath .= $paths[0][0]; + + if ($paths[0][0] != DIRECTORY_SEPARATOR) { + $commonPath .= DIRECTORY_SEPARATOR; + } + + for ($i = 0; $i < $max; $i++) { + array_shift($paths[$i]); + } + } + } + + $original = array_keys($files); + $max = count($original); + + for ($i = 0; $i < $max; $i++) { + $files[implode('/', $paths[$i])] = $files[$original[$i]]; + unset($files[$original[$i]]); + } + + ksort($files); + + return substr($commonPath, 0, -1); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/Directory.php b/vendor/phpunit/php-code-coverage/src/Node/Directory.php new file mode 100644 index 0000000000..6a9f28db58 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/Directory.php @@ -0,0 +1,483 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Node; + +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +/** + * Represents a directory in the code coverage information tree. + */ +class Directory extends AbstractNode implements \IteratorAggregate +{ + /** + * @var AbstractNode[] + */ + private $children = []; + + /** + * @var Directory[] + */ + private $directories = []; + + /** + * @var File[] + */ + private $files = []; + + /** + * @var array + */ + private $classes; + + /** + * @var array + */ + private $traits; + + /** + * @var array + */ + private $functions; + + /** + * @var array + */ + private $linesOfCode = null; + + /** + * @var int + */ + private $numFiles = -1; + + /** + * @var int + */ + private $numExecutableLines = -1; + + /** + * @var int + */ + private $numExecutedLines = -1; + + /** + * @var int + */ + private $numClasses = -1; + + /** + * @var int + */ + private $numTestedClasses = -1; + + /** + * @var int + */ + private $numTraits = -1; + + /** + * @var int + */ + private $numTestedTraits = -1; + + /** + * @var int + */ + private $numMethods = -1; + + /** + * @var int + */ + private $numTestedMethods = -1; + + /** + * @var int + */ + private $numFunctions = -1; + + /** + * @var int + */ + private $numTestedFunctions = -1; + + /** + * Returns the number of files in/under this node. + * + * @return int + */ + public function count() + { + if ($this->numFiles == -1) { + $this->numFiles = 0; + + foreach ($this->children as $child) { + $this->numFiles += count($child); + } + } + + return $this->numFiles; + } + + /** + * Returns an iterator for this node. + * + * @return \RecursiveIteratorIterator + */ + public function getIterator() + { + return new \RecursiveIteratorIterator( + new Iterator($this), + \RecursiveIteratorIterator::SELF_FIRST + ); + } + + /** + * Adds a new directory. + * + * @param string $name + * + * @return Directory + */ + public function addDirectory($name) + { + $directory = new self($name, $this); + + $this->children[] = $directory; + $this->directories[] = &$this->children[count($this->children) - 1]; + + return $directory; + } + + /** + * Adds a new file. + * + * @param string $name + * @param array $coverageData + * @param array $testData + * @param bool $cacheTokens + * + * @return File + * + * @throws InvalidArgumentException + */ + public function addFile($name, array $coverageData, array $testData, $cacheTokens) + { + $file = new File( + $name, + $this, + $coverageData, + $testData, + $cacheTokens + ); + + $this->children[] = $file; + $this->files[] = &$this->children[count($this->children) - 1]; + + $this->numExecutableLines = -1; + $this->numExecutedLines = -1; + + return $file; + } + + /** + * Returns the directories in this directory. + * + * @return array + */ + public function getDirectories() + { + return $this->directories; + } + + /** + * Returns the files in this directory. + * + * @return array + */ + public function getFiles() + { + return $this->files; + } + + /** + * Returns the child nodes of this node. + * + * @return array + */ + public function getChildNodes() + { + return $this->children; + } + + /** + * Returns the classes of this node. + * + * @return array + */ + public function getClasses() + { + if ($this->classes === null) { + $this->classes = []; + + foreach ($this->children as $child) { + $this->classes = array_merge( + $this->classes, + $child->getClasses() + ); + } + } + + return $this->classes; + } + + /** + * Returns the traits of this node. + * + * @return array + */ + public function getTraits() + { + if ($this->traits === null) { + $this->traits = []; + + foreach ($this->children as $child) { + $this->traits = array_merge( + $this->traits, + $child->getTraits() + ); + } + } + + return $this->traits; + } + + /** + * Returns the functions of this node. + * + * @return array + */ + public function getFunctions() + { + if ($this->functions === null) { + $this->functions = []; + + foreach ($this->children as $child) { + $this->functions = array_merge( + $this->functions, + $child->getFunctions() + ); + } + } + + return $this->functions; + } + + /** + * Returns the LOC/CLOC/NCLOC of this node. + * + * @return array + */ + public function getLinesOfCode() + { + if ($this->linesOfCode === null) { + $this->linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0]; + + foreach ($this->children as $child) { + $linesOfCode = $child->getLinesOfCode(); + + $this->linesOfCode['loc'] += $linesOfCode['loc']; + $this->linesOfCode['cloc'] += $linesOfCode['cloc']; + $this->linesOfCode['ncloc'] += $linesOfCode['ncloc']; + } + } + + return $this->linesOfCode; + } + + /** + * Returns the number of executable lines. + * + * @return int + */ + public function getNumExecutableLines() + { + if ($this->numExecutableLines == -1) { + $this->numExecutableLines = 0; + + foreach ($this->children as $child) { + $this->numExecutableLines += $child->getNumExecutableLines(); + } + } + + return $this->numExecutableLines; + } + + /** + * Returns the number of executed lines. + * + * @return int + */ + public function getNumExecutedLines() + { + if ($this->numExecutedLines == -1) { + $this->numExecutedLines = 0; + + foreach ($this->children as $child) { + $this->numExecutedLines += $child->getNumExecutedLines(); + } + } + + return $this->numExecutedLines; + } + + /** + * Returns the number of classes. + * + * @return int + */ + public function getNumClasses() + { + if ($this->numClasses == -1) { + $this->numClasses = 0; + + foreach ($this->children as $child) { + $this->numClasses += $child->getNumClasses(); + } + } + + return $this->numClasses; + } + + /** + * Returns the number of tested classes. + * + * @return int + */ + public function getNumTestedClasses() + { + if ($this->numTestedClasses == -1) { + $this->numTestedClasses = 0; + + foreach ($this->children as $child) { + $this->numTestedClasses += $child->getNumTestedClasses(); + } + } + + return $this->numTestedClasses; + } + + /** + * Returns the number of traits. + * + * @return int + */ + public function getNumTraits() + { + if ($this->numTraits == -1) { + $this->numTraits = 0; + + foreach ($this->children as $child) { + $this->numTraits += $child->getNumTraits(); + } + } + + return $this->numTraits; + } + + /** + * Returns the number of tested traits. + * + * @return int + */ + public function getNumTestedTraits() + { + if ($this->numTestedTraits == -1) { + $this->numTestedTraits = 0; + + foreach ($this->children as $child) { + $this->numTestedTraits += $child->getNumTestedTraits(); + } + } + + return $this->numTestedTraits; + } + + /** + * Returns the number of methods. + * + * @return int + */ + public function getNumMethods() + { + if ($this->numMethods == -1) { + $this->numMethods = 0; + + foreach ($this->children as $child) { + $this->numMethods += $child->getNumMethods(); + } + } + + return $this->numMethods; + } + + /** + * Returns the number of tested methods. + * + * @return int + */ + public function getNumTestedMethods() + { + if ($this->numTestedMethods == -1) { + $this->numTestedMethods = 0; + + foreach ($this->children as $child) { + $this->numTestedMethods += $child->getNumTestedMethods(); + } + } + + return $this->numTestedMethods; + } + + /** + * Returns the number of functions. + * + * @return int + */ + public function getNumFunctions() + { + if ($this->numFunctions == -1) { + $this->numFunctions = 0; + + foreach ($this->children as $child) { + $this->numFunctions += $child->getNumFunctions(); + } + } + + return $this->numFunctions; + } + + /** + * Returns the number of tested functions. + * + * @return int + */ + public function getNumTestedFunctions() + { + if ($this->numTestedFunctions == -1) { + $this->numTestedFunctions = 0; + + foreach ($this->children as $child) { + $this->numTestedFunctions += $child->getNumTestedFunctions(); + } + } + + return $this->numTestedFunctions; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/File.php b/vendor/phpunit/php-code-coverage/src/Node/File.php new file mode 100644 index 0000000000..44856f075c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/File.php @@ -0,0 +1,722 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Node; + +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +/** + * Represents a file in the code coverage information tree. + */ +class File extends AbstractNode +{ + /** + * @var array + */ + private $coverageData; + + /** + * @var array + */ + private $testData; + + /** + * @var int + */ + private $numExecutableLines = 0; + + /** + * @var int + */ + private $numExecutedLines = 0; + + /** + * @var array + */ + private $classes = []; + + /** + * @var array + */ + private $traits = []; + + /** + * @var array + */ + private $functions = []; + + /** + * @var array + */ + private $linesOfCode = []; + + /** + * @var int + */ + private $numClasses = null; + + /** + * @var int + */ + private $numTestedClasses = 0; + + /** + * @var int + */ + private $numTraits = null; + + /** + * @var int + */ + private $numTestedTraits = 0; + + /** + * @var int + */ + private $numMethods = null; + + /** + * @var int + */ + private $numTestedMethods = null; + + /** + * @var int + */ + private $numTestedFunctions = null; + + /** + * @var array + */ + private $startLines = []; + + /** + * @var array + */ + private $endLines = []; + + /** + * @var bool + */ + private $cacheTokens; + + /** + * Constructor. + * + * @param string $name + * @param AbstractNode $parent + * @param array $coverageData + * @param array $testData + * @param bool $cacheTokens + * + * @throws InvalidArgumentException + */ + public function __construct($name, AbstractNode $parent, array $coverageData, array $testData, $cacheTokens) + { + if (!is_bool($cacheTokens)) { + throw InvalidArgumentException::create( + 1, + 'boolean' + ); + } + + parent::__construct($name, $parent); + + $this->coverageData = $coverageData; + $this->testData = $testData; + $this->cacheTokens = $cacheTokens; + + $this->calculateStatistics(); + } + + /** + * Returns the number of files in/under this node. + * + * @return int + */ + public function count() + { + return 1; + } + + /** + * Returns the code coverage data of this node. + * + * @return array + */ + public function getCoverageData() + { + return $this->coverageData; + } + + /** + * Returns the test data of this node. + * + * @return array + */ + public function getTestData() + { + return $this->testData; + } + + /** + * Returns the classes of this node. + * + * @return array + */ + public function getClasses() + { + return $this->classes; + } + + /** + * Returns the traits of this node. + * + * @return array + */ + public function getTraits() + { + return $this->traits; + } + + /** + * Returns the functions of this node. + * + * @return array + */ + public function getFunctions() + { + return $this->functions; + } + + /** + * Returns the LOC/CLOC/NCLOC of this node. + * + * @return array + */ + public function getLinesOfCode() + { + return $this->linesOfCode; + } + + /** + * Returns the number of executable lines. + * + * @return int + */ + public function getNumExecutableLines() + { + return $this->numExecutableLines; + } + + /** + * Returns the number of executed lines. + * + * @return int + */ + public function getNumExecutedLines() + { + return $this->numExecutedLines; + } + + /** + * Returns the number of classes. + * + * @return int + */ + public function getNumClasses() + { + if ($this->numClasses === null) { + $this->numClasses = 0; + + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numClasses++; + + continue 2; + } + } + } + } + + return $this->numClasses; + } + + /** + * Returns the number of tested classes. + * + * @return int + */ + public function getNumTestedClasses() + { + return $this->numTestedClasses; + } + + /** + * Returns the number of traits. + * + * @return int + */ + public function getNumTraits() + { + if ($this->numTraits === null) { + $this->numTraits = 0; + + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numTraits++; + + continue 2; + } + } + } + } + + return $this->numTraits; + } + + /** + * Returns the number of tested traits. + * + * @return int + */ + public function getNumTestedTraits() + { + return $this->numTestedTraits; + } + + /** + * Returns the number of methods. + * + * @return int + */ + public function getNumMethods() + { + if ($this->numMethods === null) { + $this->numMethods = 0; + + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numMethods++; + } + } + } + + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0) { + $this->numMethods++; + } + } + } + } + + return $this->numMethods; + } + + /** + * Returns the number of tested methods. + * + * @return int + */ + public function getNumTestedMethods() + { + if ($this->numTestedMethods === null) { + $this->numTestedMethods = 0; + + foreach ($this->classes as $class) { + foreach ($class['methods'] as $method) { + if ($method['executableLines'] > 0 && + $method['coverage'] == 100) { + $this->numTestedMethods++; + } + } + } + + foreach ($this->traits as $trait) { + foreach ($trait['methods'] as $method) { + if ($method['executableLines'] > 0 && + $method['coverage'] == 100) { + $this->numTestedMethods++; + } + } + } + } + + return $this->numTestedMethods; + } + + /** + * Returns the number of functions. + * + * @return int + */ + public function getNumFunctions() + { + return count($this->functions); + } + + /** + * Returns the number of tested functions. + * + * @return int + */ + public function getNumTestedFunctions() + { + if ($this->numTestedFunctions === null) { + $this->numTestedFunctions = 0; + + foreach ($this->functions as $function) { + if ($function['executableLines'] > 0 && + $function['coverage'] == 100) { + $this->numTestedFunctions++; + } + } + } + + return $this->numTestedFunctions; + } + + /** + * Calculates coverage statistics for the file. + */ + protected function calculateStatistics() + { + $classStack = $functionStack = []; + + if ($this->cacheTokens) { + $tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath()); + } else { + $tokens = new \PHP_Token_Stream($this->getPath()); + } + + $this->processClasses($tokens); + $this->processTraits($tokens); + $this->processFunctions($tokens); + $this->linesOfCode = $tokens->getLinesOfCode(); + unset($tokens); + + for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) { + if (isset($this->startLines[$lineNumber])) { + // Start line of a class. + if (isset($this->startLines[$lineNumber]['className'])) { + if (isset($currentClass)) { + $classStack[] = &$currentClass; + } + + $currentClass = &$this->startLines[$lineNumber]; + } // Start line of a trait. + elseif (isset($this->startLines[$lineNumber]['traitName'])) { + $currentTrait = &$this->startLines[$lineNumber]; + } // Start line of a method. + elseif (isset($this->startLines[$lineNumber]['methodName'])) { + $currentMethod = &$this->startLines[$lineNumber]; + } // Start line of a function. + elseif (isset($this->startLines[$lineNumber]['functionName'])) { + if (isset($currentFunction)) { + $functionStack[] = &$currentFunction; + } + + $currentFunction = &$this->startLines[$lineNumber]; + } + } + + if (isset($this->coverageData[$lineNumber])) { + if (isset($currentClass)) { + $currentClass['executableLines']++; + } + + if (isset($currentTrait)) { + $currentTrait['executableLines']++; + } + + if (isset($currentMethod)) { + $currentMethod['executableLines']++; + } + + if (isset($currentFunction)) { + $currentFunction['executableLines']++; + } + + $this->numExecutableLines++; + + if (count($this->coverageData[$lineNumber]) > 0) { + if (isset($currentClass)) { + $currentClass['executedLines']++; + } + + if (isset($currentTrait)) { + $currentTrait['executedLines']++; + } + + if (isset($currentMethod)) { + $currentMethod['executedLines']++; + } + + if (isset($currentFunction)) { + $currentFunction['executedLines']++; + } + + $this->numExecutedLines++; + } + } + + if (isset($this->endLines[$lineNumber])) { + // End line of a class. + if (isset($this->endLines[$lineNumber]['className'])) { + unset($currentClass); + + if ($classStack) { + end($classStack); + $key = key($classStack); + $currentClass = &$classStack[$key]; + unset($classStack[$key]); + } + } // End line of a trait. + elseif (isset($this->endLines[$lineNumber]['traitName'])) { + unset($currentTrait); + } // End line of a method. + elseif (isset($this->endLines[$lineNumber]['methodName'])) { + unset($currentMethod); + } // End line of a function. + elseif (isset($this->endLines[$lineNumber]['functionName'])) { + unset($currentFunction); + + if ($functionStack) { + end($functionStack); + $key = key($functionStack); + $currentFunction = &$functionStack[$key]; + unset($functionStack[$key]); + } + } + } + } + + foreach ($this->traits as &$trait) { + foreach ($trait['methods'] as &$method) { + if ($method['executableLines'] > 0) { + $method['coverage'] = ($method['executedLines'] / + $method['executableLines']) * 100; + } else { + $method['coverage'] = 100; + } + + $method['crap'] = $this->crap( + $method['ccn'], + $method['coverage'] + ); + + $trait['ccn'] += $method['ccn']; + } + + if ($trait['executableLines'] > 0) { + $trait['coverage'] = ($trait['executedLines'] / + $trait['executableLines']) * 100; + + if ($trait['coverage'] == 100) { + $this->numTestedClasses++; + } + } else { + $trait['coverage'] = 100; + } + + $trait['crap'] = $this->crap( + $trait['ccn'], + $trait['coverage'] + ); + } + + foreach ($this->classes as &$class) { + foreach ($class['methods'] as &$method) { + if ($method['executableLines'] > 0) { + $method['coverage'] = ($method['executedLines'] / + $method['executableLines']) * 100; + } else { + $method['coverage'] = 100; + } + + $method['crap'] = $this->crap( + $method['ccn'], + $method['coverage'] + ); + + $class['ccn'] += $method['ccn']; + } + + if ($class['executableLines'] > 0) { + $class['coverage'] = ($class['executedLines'] / + $class['executableLines']) * 100; + + if ($class['coverage'] == 100) { + $this->numTestedClasses++; + } + } else { + $class['coverage'] = 100; + } + + $class['crap'] = $this->crap( + $class['ccn'], + $class['coverage'] + ); + } + } + + /** + * @param \PHP_Token_Stream $tokens + */ + protected function processClasses(\PHP_Token_Stream $tokens) + { + $classes = $tokens->getClasses(); + unset($tokens); + + $link = $this->getId() . '.html#'; + + foreach ($classes as $className => $class) { + $this->classes[$className] = [ + 'className' => $className, + 'methods' => [], + 'startLine' => $class['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'ccn' => 0, + 'coverage' => 0, + 'crap' => 0, + 'package' => $class['package'], + 'link' => $link . $class['startLine'] + ]; + + $this->startLines[$class['startLine']] = &$this->classes[$className]; + $this->endLines[$class['endLine']] = &$this->classes[$className]; + + foreach ($class['methods'] as $methodName => $method) { + $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link); + + $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName]; + $this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName]; + } + } + } + + /** + * @param \PHP_Token_Stream $tokens + */ + protected function processTraits(\PHP_Token_Stream $tokens) + { + $traits = $tokens->getTraits(); + unset($tokens); + + $link = $this->getId() . '.html#'; + + foreach ($traits as $traitName => $trait) { + $this->traits[$traitName] = [ + 'traitName' => $traitName, + 'methods' => [], + 'startLine' => $trait['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'ccn' => 0, + 'coverage' => 0, + 'crap' => 0, + 'package' => $trait['package'], + 'link' => $link . $trait['startLine'] + ]; + + $this->startLines[$trait['startLine']] = &$this->traits[$traitName]; + $this->endLines[$trait['endLine']] = &$this->traits[$traitName]; + + foreach ($trait['methods'] as $methodName => $method) { + $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link); + + $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName]; + $this->endLines[$method['endLine']] = &$this->traits[$traitName]['methods'][$methodName]; + } + } + } + + /** + * @param \PHP_Token_Stream $tokens + */ + protected function processFunctions(\PHP_Token_Stream $tokens) + { + $functions = $tokens->getFunctions(); + unset($tokens); + + $link = $this->getId() . '.html#'; + + foreach ($functions as $functionName => $function) { + $this->functions[$functionName] = [ + 'functionName' => $functionName, + 'signature' => $function['signature'], + 'startLine' => $function['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'ccn' => $function['ccn'], + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $function['startLine'] + ]; + + $this->startLines[$function['startLine']] = &$this->functions[$functionName]; + $this->endLines[$function['endLine']] = &$this->functions[$functionName]; + } + } + + /** + * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code + * based on its cyclomatic complexity and percentage of code coverage. + * + * @param int $ccn + * @param float $coverage + * + * @return string + */ + protected function crap($ccn, $coverage) + { + if ($coverage == 0) { + return (string) (pow($ccn, 2) + $ccn); + } + + if ($coverage >= 95) { + return (string) $ccn; + } + + return sprintf( + '%01.2F', + pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn + ); + } + + /** + * @param string $methodName + * @param array $method + * @param string $link + * + * @return array + */ + private function newMethod($methodName, array $method, $link) + { + return [ + 'methodName' => $methodName, + 'visibility' => $method['visibility'], + 'signature' => $method['signature'], + 'startLine' => $method['startLine'], + 'endLine' => $method['endLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'ccn' => $method['ccn'], + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $method['startLine'], + ]; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Node/Iterator.php b/vendor/phpunit/php-code-coverage/src/Node/Iterator.php new file mode 100644 index 0000000000..e246380591 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Node/Iterator.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Node; + +/** + * Recursive iterator for node object graphs. + */ +class Iterator implements \RecursiveIterator +{ + /** + * @var int + */ + private $position; + + /** + * @var AbstractNode[] + */ + private $nodes; + + /** + * @param Directory $node + */ + public function __construct(Directory $node) + { + $this->nodes = $node->getChildNodes(); + } + + /** + * Rewinds the Iterator to the first element. + */ + public function rewind() + { + $this->position = 0; + } + + /** + * Checks if there is a current element after calls to rewind() or next(). + * + * @return bool + */ + public function valid() + { + return $this->position < count($this->nodes); + } + + /** + * Returns the key of the current element. + * + * @return int + */ + public function key() + { + return $this->position; + } + + /** + * Returns the current element. + * + * @return \PHPUnit_Framework_Test + */ + public function current() + { + return $this->valid() ? $this->nodes[$this->position] : null; + } + + /** + * Moves forward to next element. + */ + public function next() + { + $this->position++; + } + + /** + * Returns the sub iterator for the current element. + * + * @return Iterator + */ + public function getChildren() + { + return new self( + $this->nodes[$this->position] + ); + } + + /** + * Checks whether the current element has children. + * + * @return bool + */ + public function hasChildren() + { + return $this->nodes[$this->position] instanceof Directory; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Clover.php b/vendor/phpunit/php-code-coverage/src/Report/Clover.php new file mode 100644 index 0000000000..054b1dfde7 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Clover.php @@ -0,0 +1,251 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report; + +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Node\File; + +/** + * Generates a Clover XML logfile from a code coverage object. + */ +class Clover +{ + /** + * @param CodeCoverage $coverage + * @param string $target + * @param string $name + * + * @return string + */ + public function process(CodeCoverage $coverage, $target = null, $name = null) + { + $xmlDocument = new \DOMDocument('1.0', 'UTF-8'); + $xmlDocument->formatOutput = true; + + $xmlCoverage = $xmlDocument->createElement('coverage'); + $xmlCoverage->setAttribute('generated', (int) $_SERVER['REQUEST_TIME']); + $xmlDocument->appendChild($xmlCoverage); + + $xmlProject = $xmlDocument->createElement('project'); + $xmlProject->setAttribute('timestamp', (int) $_SERVER['REQUEST_TIME']); + + if (is_string($name)) { + $xmlProject->setAttribute('name', $name); + } + + $xmlCoverage->appendChild($xmlProject); + + $packages = []; + $report = $coverage->getReport(); + unset($coverage); + + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + + /* @var File $item */ + + $xmlFile = $xmlDocument->createElement('file'); + $xmlFile->setAttribute('name', $item->getPath()); + + $classes = $item->getClassesAndTraits(); + $coverage = $item->getCoverageData(); + $lines = []; + $namespace = 'global'; + + foreach ($classes as $className => $class) { + $classStatements = 0; + $coveredClassStatements = 0; + $coveredMethods = 0; + $classMethods = 0; + + foreach ($class['methods'] as $methodName => $method) { + if ($method['executableLines'] == 0) { + continue; + } + + $classMethods++; + $classStatements += $method['executableLines']; + $coveredClassStatements += $method['executedLines']; + + if ($method['coverage'] == 100) { + $coveredMethods++; + } + + $methodCount = 0; + + foreach (range($method['startLine'], $method['endLine']) as $line) { + if (isset($coverage[$line]) && ($coverage[$line] !== null)) { + $methodCount = max($methodCount, count($coverage[$line])); + } + } + + $lines[$method['startLine']] = [ + 'ccn' => $method['ccn'], + 'count' => $methodCount, + 'crap' => $method['crap'], + 'type' => 'method', + 'visibility' => $method['visibility'], + 'name' => $methodName + ]; + } + + if (!empty($class['package']['namespace'])) { + $namespace = $class['package']['namespace']; + } + + $xmlClass = $xmlDocument->createElement('class'); + $xmlClass->setAttribute('name', $className); + $xmlClass->setAttribute('namespace', $namespace); + + if (!empty($class['package']['fullPackage'])) { + $xmlClass->setAttribute( + 'fullPackage', + $class['package']['fullPackage'] + ); + } + + if (!empty($class['package']['category'])) { + $xmlClass->setAttribute( + 'category', + $class['package']['category'] + ); + } + + if (!empty($class['package']['package'])) { + $xmlClass->setAttribute( + 'package', + $class['package']['package'] + ); + } + + if (!empty($class['package']['subpackage'])) { + $xmlClass->setAttribute( + 'subpackage', + $class['package']['subpackage'] + ); + } + + $xmlFile->appendChild($xmlClass); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('complexity', $class['ccn']); + $xmlMetrics->setAttribute('methods', $classMethods); + $xmlMetrics->setAttribute('coveredmethods', $coveredMethods); + $xmlMetrics->setAttribute('conditionals', 0); + $xmlMetrics->setAttribute('coveredconditionals', 0); + $xmlMetrics->setAttribute('statements', $classStatements); + $xmlMetrics->setAttribute('coveredstatements', $coveredClassStatements); + $xmlMetrics->setAttribute('elements', $classMethods + $classStatements /* + conditionals */); + $xmlMetrics->setAttribute('coveredelements', $coveredMethods + $coveredClassStatements /* + coveredconditionals */); + $xmlClass->appendChild($xmlMetrics); + } + + foreach ($coverage as $line => $data) { + if ($data === null || isset($lines[$line])) { + continue; + } + + $lines[$line] = [ + 'count' => count($data), 'type' => 'stmt' + ]; + } + + ksort($lines); + + foreach ($lines as $line => $data) { + $xmlLine = $xmlDocument->createElement('line'); + $xmlLine->setAttribute('num', $line); + $xmlLine->setAttribute('type', $data['type']); + + if (isset($data['name'])) { + $xmlLine->setAttribute('name', $data['name']); + } + + if (isset($data['visibility'])) { + $xmlLine->setAttribute('visibility', $data['visibility']); + } + + if (isset($data['ccn'])) { + $xmlLine->setAttribute('complexity', $data['ccn']); + } + + if (isset($data['crap'])) { + $xmlLine->setAttribute('crap', $data['crap']); + } + + $xmlLine->setAttribute('count', $data['count']); + $xmlFile->appendChild($xmlLine); + } + + $linesOfCode = $item->getLinesOfCode(); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); + $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); + $xmlMetrics->setAttribute('classes', $item->getNumClassesAndTraits()); + $xmlMetrics->setAttribute('methods', $item->getNumMethods()); + $xmlMetrics->setAttribute('coveredmethods', $item->getNumTestedMethods()); + $xmlMetrics->setAttribute('conditionals', 0); + $xmlMetrics->setAttribute('coveredconditionals', 0); + $xmlMetrics->setAttribute('statements', $item->getNumExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', $item->getNumExecutedLines()); + $xmlMetrics->setAttribute('elements', $item->getNumMethods() + $item->getNumExecutableLines() /* + conditionals */); + $xmlMetrics->setAttribute('coveredelements', $item->getNumTestedMethods() + $item->getNumExecutedLines() /* + coveredconditionals */); + $xmlFile->appendChild($xmlMetrics); + + if ($namespace == 'global') { + $xmlProject->appendChild($xmlFile); + } else { + if (!isset($packages[$namespace])) { + $packages[$namespace] = $xmlDocument->createElement( + 'package' + ); + + $packages[$namespace]->setAttribute('name', $namespace); + $xmlProject->appendChild($packages[$namespace]); + } + + $packages[$namespace]->appendChild($xmlFile); + } + } + + $linesOfCode = $report->getLinesOfCode(); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('files', count($report)); + $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); + $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); + $xmlMetrics->setAttribute('classes', $report->getNumClassesAndTraits()); + $xmlMetrics->setAttribute('methods', $report->getNumMethods()); + $xmlMetrics->setAttribute('coveredmethods', $report->getNumTestedMethods()); + $xmlMetrics->setAttribute('conditionals', 0); + $xmlMetrics->setAttribute('coveredconditionals', 0); + $xmlMetrics->setAttribute('statements', $report->getNumExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', $report->getNumExecutedLines()); + $xmlMetrics->setAttribute('elements', $report->getNumMethods() + $report->getNumExecutableLines() /* + conditionals */); + $xmlMetrics->setAttribute('coveredelements', $report->getNumTestedMethods() + $report->getNumExecutedLines() /* + coveredconditionals */); + $xmlProject->appendChild($xmlMetrics); + + $buffer = $xmlDocument->saveXML(); + + if ($target !== null) { + if (!is_dir(dirname($target))) { + mkdir(dirname($target), 0777, true); + } + + file_put_contents($target, $buffer); + } + + return $buffer; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Crap4j.php b/vendor/phpunit/php-code-coverage/src/Report/Crap4j.php new file mode 100644 index 0000000000..7adf78fe39 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Crap4j.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report; + +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Node\File; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +class Crap4j +{ + /** + * @var int + */ + private $threshold; + + /** + * @param int $threshold + */ + public function __construct($threshold = 30) + { + if (!is_int($threshold)) { + throw InvalidArgumentException::create( + 1, + 'integer' + ); + } + + $this->threshold = $threshold; + } + + /** + * @param CodeCoverage $coverage + * @param string $target + * @param string $name + * + * @return string + */ + public function process(CodeCoverage $coverage, $target = null, $name = null) + { + $document = new \DOMDocument('1.0', 'UTF-8'); + $document->formatOutput = true; + + $root = $document->createElement('crap_result'); + $document->appendChild($root); + + $project = $document->createElement('project', is_string($name) ? $name : ''); + $root->appendChild($project); + $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s', (int) $_SERVER['REQUEST_TIME']))); + + $stats = $document->createElement('stats'); + $methodsNode = $document->createElement('methods'); + + $report = $coverage->getReport(); + unset($coverage); + + $fullMethodCount = 0; + $fullCrapMethodCount = 0; + $fullCrapLoad = 0; + $fullCrap = 0; + + foreach ($report as $item) { + $namespace = 'global'; + + if (!$item instanceof File) { + continue; + } + + $file = $document->createElement('file'); + $file->setAttribute('name', $item->getPath()); + + $classes = $item->getClassesAndTraits(); + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + $crapLoad = $this->getCrapLoad($method['crap'], $method['ccn'], $method['coverage']); + + $fullCrap += $method['crap']; + $fullCrapLoad += $crapLoad; + $fullMethodCount++; + + if ($method['crap'] >= $this->threshold) { + $fullCrapMethodCount++; + } + + $methodNode = $document->createElement('method'); + + if (!empty($class['package']['namespace'])) { + $namespace = $class['package']['namespace']; + } + + $methodNode->appendChild($document->createElement('package', $namespace)); + $methodNode->appendChild($document->createElement('className', $className)); + $methodNode->appendChild($document->createElement('methodName', $methodName)); + $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature']))); + $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature']))); + $methodNode->appendChild($document->createElement('crap', $this->roundValue($method['crap']))); + $methodNode->appendChild($document->createElement('complexity', $method['ccn'])); + $methodNode->appendChild($document->createElement('coverage', $this->roundValue($method['coverage']))); + $methodNode->appendChild($document->createElement('crapLoad', round($crapLoad))); + + $methodsNode->appendChild($methodNode); + } + } + } + + $stats->appendChild($document->createElement('name', 'Method Crap Stats')); + $stats->appendChild($document->createElement('methodCount', $fullMethodCount)); + $stats->appendChild($document->createElement('crapMethodCount', $fullCrapMethodCount)); + $stats->appendChild($document->createElement('crapLoad', round($fullCrapLoad))); + $stats->appendChild($document->createElement('totalCrap', $fullCrap)); + + if ($fullMethodCount > 0) { + $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount); + } else { + $crapMethodPercent = 0; + } + + $stats->appendChild($document->createElement('crapMethodPercent', $crapMethodPercent)); + + $root->appendChild($stats); + $root->appendChild($methodsNode); + + $buffer = $document->saveXML(); + + if ($target !== null) { + if (!is_dir(dirname($target))) { + mkdir(dirname($target), 0777, true); + } + + file_put_contents($target, $buffer); + } + + return $buffer; + } + + /** + * @param float $crapValue + * @param int $cyclomaticComplexity + * @param float $coveragePercent + * + * @return float + */ + private function getCrapLoad($crapValue, $cyclomaticComplexity, $coveragePercent) + { + $crapLoad = 0; + + if ($crapValue >= $this->threshold) { + $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); + $crapLoad += $cyclomaticComplexity / $this->threshold; + } + + return $crapLoad; + } + + /** + * @param float $value + * + * @return float + */ + private function roundValue($value) + { + return round($value, 2); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php new file mode 100644 index 0000000000..adcfe42478 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\CodeCoverage\RuntimeException; + +/** + * Generates an HTML report from a code coverage object. + */ +class Facade +{ + /** + * @var string + */ + private $templatePath; + + /** + * @var string + */ + private $generator; + + /** + * @var int + */ + private $lowUpperBound; + + /** + * @var int + */ + private $highLowerBound; + + /** + * Constructor. + * + * @param int $lowUpperBound + * @param int $highLowerBound + * @param string $generator + */ + public function __construct($lowUpperBound = 50, $highLowerBound = 90, $generator = '') + { + $this->generator = $generator; + $this->highLowerBound = $highLowerBound; + $this->lowUpperBound = $lowUpperBound; + $this->templatePath = __DIR__ . '/Renderer/Template/'; + } + + /** + * @param CodeCoverage $coverage + * @param string $target + */ + public function process(CodeCoverage $coverage, $target) + { + $target = $this->getDirectory($target); + $report = $coverage->getReport(); + unset($coverage); + + if (!isset($_SERVER['REQUEST_TIME'])) { + $_SERVER['REQUEST_TIME'] = time(); + } + + $date = date('D M j G:i:s T Y', $_SERVER['REQUEST_TIME']); + + $dashboard = new Dashboard( + $this->templatePath, + $this->generator, + $date, + $this->lowUpperBound, + $this->highLowerBound + ); + + $directory = new Directory( + $this->templatePath, + $this->generator, + $date, + $this->lowUpperBound, + $this->highLowerBound + ); + + $file = new File( + $this->templatePath, + $this->generator, + $date, + $this->lowUpperBound, + $this->highLowerBound + ); + + $directory->render($report, $target . 'index.html'); + $dashboard->render($report, $target . 'dashboard.html'); + + foreach ($report as $node) { + $id = $node->getId(); + + if ($node instanceof DirectoryNode) { + if (!file_exists($target . $id)) { + mkdir($target . $id, 0777, true); + } + + $directory->render($node, $target . $id . '/index.html'); + $dashboard->render($node, $target . $id . '/dashboard.html'); + } else { + $dir = dirname($target . $id); + + if (!file_exists($dir)) { + mkdir($dir, 0777, true); + } + + $file->render($node, $target . $id . '.html'); + } + } + + $this->copyFiles($target); + } + + /** + * @param string $target + */ + private function copyFiles($target) + { + $dir = $this->getDirectory($target . 'css'); + copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css'); + copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css'); + copy($this->templatePath . 'css/style.css', $dir . 'style.css'); + + $dir = $this->getDirectory($target . 'fonts'); + copy($this->templatePath . 'fonts/glyphicons-halflings-regular.eot', $dir . 'glyphicons-halflings-regular.eot'); + copy($this->templatePath . 'fonts/glyphicons-halflings-regular.svg', $dir . 'glyphicons-halflings-regular.svg'); + copy($this->templatePath . 'fonts/glyphicons-halflings-regular.ttf', $dir . 'glyphicons-halflings-regular.ttf'); + copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff', $dir . 'glyphicons-halflings-regular.woff'); + copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff2', $dir . 'glyphicons-halflings-regular.woff2'); + + $dir = $this->getDirectory($target . 'js'); + copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js'); + copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js'); + copy($this->templatePath . 'js/holder.min.js', $dir . 'holder.min.js'); + copy($this->templatePath . 'js/html5shiv.min.js', $dir . 'html5shiv.min.js'); + copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js'); + copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js'); + copy($this->templatePath . 'js/respond.min.js', $dir . 'respond.min.js'); + } + + /** + * @param string $directory + * + * @return string + * + * @throws RuntimeException + */ + private function getDirectory($directory) + { + if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) { + $directory .= DIRECTORY_SEPARATOR; + } + + if (is_dir($directory)) { + return $directory; + } + + if (@mkdir($directory, 0777, true)) { + return $directory; + } + + throw new RuntimeException( + sprintf( + 'Directory "%s" does not exist.', + $directory + ) + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php new file mode 100644 index 0000000000..da0937e755 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php @@ -0,0 +1,298 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use SebastianBergmann\CodeCoverage\Node\AbstractNode; +use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\Environment\Runtime; +use SebastianBergmann\Version; + +/** + * Base class for node renderers. + */ +abstract class Renderer +{ + /** + * @var string + */ + protected $templatePath; + + /** + * @var string + */ + protected $generator; + + /** + * @var string + */ + protected $date; + + /** + * @var int + */ + protected $lowUpperBound; + + /** + * @var int + */ + protected $highLowerBound; + + /** + * @var string + */ + protected $version; + + /** + * Constructor. + * + * @param string $templatePath + * @param string $generator + * @param string $date + * @param int $lowUpperBound + * @param int $highLowerBound + */ + public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound) + { + $version = new Version('4.0.8', dirname(dirname(dirname(dirname(__DIR__))))); + + $this->templatePath = $templatePath; + $this->generator = $generator; + $this->date = $date; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->version = $version->getVersion(); + } + + /** + * @param \Text_Template $template + * @param array $data + * + * @return string + */ + protected function renderItemTemplate(\Text_Template $template, array $data) + { + $numSeparator = ' / '; + + if (isset($data['numClasses']) && $data['numClasses'] > 0) { + $classesLevel = $this->getColorLevel($data['testedClassesPercent']); + + $classesNumber = $data['numTestedClasses'] . $numSeparator . + $data['numClasses']; + + $classesBar = $this->getCoverageBar( + $data['testedClassesPercent'] + ); + } else { + $classesLevel = ''; + $classesNumber = '0' . $numSeparator . '0'; + $classesBar = ''; + $data['testedClassesPercentAsString'] = 'n/a'; + } + + if ($data['numMethods'] > 0) { + $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']); + + $methodsNumber = $data['numTestedMethods'] . $numSeparator . + $data['numMethods']; + + $methodsBar = $this->getCoverageBar( + $data['testedMethodsPercent'] + ); + } else { + $methodsLevel = ''; + $methodsNumber = '0' . $numSeparator . '0'; + $methodsBar = ''; + $data['testedMethodsPercentAsString'] = 'n/a'; + } + + if ($data['numExecutableLines'] > 0) { + $linesLevel = $this->getColorLevel($data['linesExecutedPercent']); + + $linesNumber = $data['numExecutedLines'] . $numSeparator . + $data['numExecutableLines']; + + $linesBar = $this->getCoverageBar( + $data['linesExecutedPercent'] + ); + } else { + $linesLevel = ''; + $linesNumber = '0' . $numSeparator . '0'; + $linesBar = ''; + $data['linesExecutedPercentAsString'] = 'n/a'; + } + + $template->setVar( + [ + 'icon' => isset($data['icon']) ? $data['icon'] : '', + 'crap' => isset($data['crap']) ? $data['crap'] : '', + 'name' => $data['name'], + 'lines_bar' => $linesBar, + 'lines_executed_percent' => $data['linesExecutedPercentAsString'], + 'lines_level' => $linesLevel, + 'lines_number' => $linesNumber, + 'methods_bar' => $methodsBar, + 'methods_tested_percent' => $data['testedMethodsPercentAsString'], + 'methods_level' => $methodsLevel, + 'methods_number' => $methodsNumber, + 'classes_bar' => $classesBar, + 'classes_tested_percent' => isset($data['testedClassesPercentAsString']) ? $data['testedClassesPercentAsString'] : '', + 'classes_level' => $classesLevel, + 'classes_number' => $classesNumber + ] + ); + + return $template->render(); + } + + /** + * @param \Text_Template $template + * @param AbstractNode $node + */ + protected function setCommonTemplateVariables(\Text_Template $template, AbstractNode $node) + { + $template->setVar( + [ + 'id' => $node->getId(), + 'full_path' => $node->getPath(), + 'path_to_root' => $this->getPathToRoot($node), + 'breadcrumbs' => $this->getBreadcrumbs($node), + 'date' => $this->date, + 'version' => $this->version, + 'runtime' => $this->getRuntimeString(), + 'generator' => $this->generator, + 'low_upper_bound' => $this->lowUpperBound, + 'high_lower_bound' => $this->highLowerBound + ] + ); + } + + protected function getBreadcrumbs(AbstractNode $node) + { + $breadcrumbs = ''; + $path = $node->getPathAsArray(); + $pathToRoot = []; + $max = count($path); + + if ($node instanceof FileNode) { + $max--; + } + + for ($i = 0; $i < $max; $i++) { + $pathToRoot[] = str_repeat('../', $i); + } + + foreach ($path as $step) { + if ($step !== $node) { + $breadcrumbs .= $this->getInactiveBreadcrumb( + $step, + array_pop($pathToRoot) + ); + } else { + $breadcrumbs .= $this->getActiveBreadcrumb($step); + } + } + + return $breadcrumbs; + } + + protected function getActiveBreadcrumb(AbstractNode $node) + { + $buffer = sprintf( + '
  • %s
  • ' . "\n", + $node->getName() + ); + + if ($node instanceof DirectoryNode) { + $buffer .= '
  • (Dashboard)
  • ' . "\n"; + } + + return $buffer; + } + + protected function getInactiveBreadcrumb(AbstractNode $node, $pathToRoot) + { + return sprintf( + '
  • %s
  • ' . "\n", + $pathToRoot, + $node->getName() + ); + } + + protected function getPathToRoot(AbstractNode $node) + { + $id = $node->getId(); + $depth = substr_count($id, '/'); + + if ($id != 'index' && + $node instanceof DirectoryNode) { + $depth++; + } + + return str_repeat('../', $depth); + } + + protected function getCoverageBar($percent) + { + $level = $this->getColorLevel($percent); + + $template = new \Text_Template( + $this->templatePath . 'coverage_bar.html', + '{{', + '}}' + ); + + $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]); + + return $template->render(); + } + + /** + * @param int $percent + * + * @return string + */ + protected function getColorLevel($percent) + { + if ($percent <= $this->lowUpperBound) { + return 'danger'; + } elseif ($percent > $this->lowUpperBound && + $percent < $this->highLowerBound) { + return 'warning'; + } else { + return 'success'; + } + } + + /** + * @return string + */ + private function getRuntimeString() + { + $runtime = new Runtime; + + $buffer = sprintf( + '%s %s', + $runtime->getVendorUrl(), + $runtime->getName(), + $runtime->getVersion() + ); + + if ($runtime->hasXdebug() && !$runtime->hasPHPDBGCodeCoverage()) { + $buffer .= sprintf( + ' with Xdebug %s', + phpversion('xdebug') + ); + } + + return $buffer; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php new file mode 100644 index 0000000000..7cde175592 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php @@ -0,0 +1,302 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use SebastianBergmann\CodeCoverage\Node\AbstractNode; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; + +/** + * Renders the dashboard for a directory node. + */ +class Dashboard extends Renderer +{ + /** + * @param DirectoryNode $node + * @param string $file + */ + public function render(DirectoryNode $node, $file) + { + $classes = $node->getClassesAndTraits(); + $template = new \Text_Template( + $this->templatePath . 'dashboard.html', + '{{', + '}}' + ); + + $this->setCommonTemplateVariables($template, $node); + + $baseLink = $node->getId() . '/'; + $complexity = $this->complexity($classes, $baseLink); + $coverageDistribution = $this->coverageDistribution($classes); + $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); + $projectRisks = $this->projectRisks($classes, $baseLink); + + $template->setVar( + [ + 'insufficient_coverage_classes' => $insufficientCoverage['class'], + 'insufficient_coverage_methods' => $insufficientCoverage['method'], + 'project_risks_classes' => $projectRisks['class'], + 'project_risks_methods' => $projectRisks['method'], + 'complexity_class' => $complexity['class'], + 'complexity_method' => $complexity['method'], + 'class_coverage_distribution' => $coverageDistribution['class'], + 'method_coverage_distribution' => $coverageDistribution['method'] + ] + ); + + $template->renderTo($file); + } + + /** + * Returns the data for the Class/Method Complexity charts. + * + * @param array $classes + * @param string $baseLink + * + * @return array + */ + protected function complexity(array $classes, $baseLink) + { + $result = ['class' => [], 'method' => []]; + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($className != '*') { + $methodName = $className . '::' . $methodName; + } + + $result['method'][] = [ + $method['coverage'], + $method['ccn'], + sprintf( + '%s', + str_replace($baseLink, '', $method['link']), + $methodName + ) + ]; + } + + $result['class'][] = [ + $class['coverage'], + $class['ccn'], + sprintf( + '%s', + str_replace($baseLink, '', $class['link']), + $className + ) + ]; + } + + return [ + 'class' => json_encode($result['class']), + 'method' => json_encode($result['method']) + ]; + } + + /** + * Returns the data for the Class / Method Coverage Distribution chart. + * + * @param array $classes + * + * @return array + */ + protected function coverageDistribution(array $classes) + { + $result = [ + 'class' => [ + '0%' => 0, + '0-10%' => 0, + '10-20%' => 0, + '20-30%' => 0, + '30-40%' => 0, + '40-50%' => 0, + '50-60%' => 0, + '60-70%' => 0, + '70-80%' => 0, + '80-90%' => 0, + '90-100%' => 0, + '100%' => 0 + ], + 'method' => [ + '0%' => 0, + '0-10%' => 0, + '10-20%' => 0, + '20-30%' => 0, + '30-40%' => 0, + '40-50%' => 0, + '50-60%' => 0, + '60-70%' => 0, + '70-80%' => 0, + '80-90%' => 0, + '90-100%' => 0, + '100%' => 0 + ] + ]; + + foreach ($classes as $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] == 0) { + $result['method']['0%']++; + } elseif ($method['coverage'] == 100) { + $result['method']['100%']++; + } else { + $key = floor($method['coverage'] / 10) * 10; + $key = $key . '-' . ($key + 10) . '%'; + $result['method'][$key]++; + } + } + + if ($class['coverage'] == 0) { + $result['class']['0%']++; + } elseif ($class['coverage'] == 100) { + $result['class']['100%']++; + } else { + $key = floor($class['coverage'] / 10) * 10; + $key = $key . '-' . ($key + 10) . '%'; + $result['class'][$key]++; + } + } + + return [ + 'class' => json_encode(array_values($result['class'])), + 'method' => json_encode(array_values($result['method'])) + ]; + } + + /** + * Returns the classes / methods with insufficient coverage. + * + * @param array $classes + * @param string $baseLink + * + * @return array + */ + protected function insufficientCoverage(array $classes, $baseLink) + { + $leastTestedClasses = []; + $leastTestedMethods = []; + $result = ['class' => '', 'method' => '']; + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] < $this->highLowerBound) { + if ($className != '*') { + $key = $className . '::' . $methodName; + } else { + $key = $methodName; + } + + $leastTestedMethods[$key] = $method['coverage']; + } + } + + if ($class['coverage'] < $this->highLowerBound) { + $leastTestedClasses[$className] = $class['coverage']; + } + } + + asort($leastTestedClasses); + asort($leastTestedMethods); + + foreach ($leastTestedClasses as $className => $coverage) { + $result['class'] .= sprintf( + ' %s%d%%' . "\n", + str_replace($baseLink, '', $classes[$className]['link']), + $className, + $coverage + ); + } + + foreach ($leastTestedMethods as $methodName => $coverage) { + list($class, $method) = explode('::', $methodName); + + $result['method'] .= sprintf( + ' %s%d%%' . "\n", + str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + $methodName, + $method, + $coverage + ); + } + + return $result; + } + + /** + * Returns the project risks according to the CRAP index. + * + * @param array $classes + * @param string $baseLink + * + * @return array + */ + protected function projectRisks(array $classes, $baseLink) + { + $classRisks = []; + $methodRisks = []; + $result = ['class' => '', 'method' => '']; + + foreach ($classes as $className => $class) { + foreach ($class['methods'] as $methodName => $method) { + if ($method['coverage'] < $this->highLowerBound && + $method['ccn'] > 1) { + if ($className != '*') { + $key = $className . '::' . $methodName; + } else { + $key = $methodName; + } + + $methodRisks[$key] = $method['crap']; + } + } + + if ($class['coverage'] < $this->highLowerBound && + $class['ccn'] > count($class['methods'])) { + $classRisks[$className] = $class['crap']; + } + } + + arsort($classRisks); + arsort($methodRisks); + + foreach ($classRisks as $className => $crap) { + $result['class'] .= sprintf( + ' %s%d' . "\n", + str_replace($baseLink, '', $classes[$className]['link']), + $className, + $crap + ); + } + + foreach ($methodRisks as $methodName => $crap) { + list($class, $method) = explode('::', $methodName); + + $result['method'] .= sprintf( + ' %s%d' . "\n", + str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), + $methodName, + $method, + $crap + ); + } + + return $result; + } + + protected function getActiveBreadcrumb(AbstractNode $node) + { + return sprintf( + '
  • %s
  • ' . "\n" . + '
  • (Dashboard)
  • ' . "\n", + $node->getName() + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php new file mode 100644 index 0000000000..a4b1b96f4d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use SebastianBergmann\CodeCoverage\Node\AbstractNode as Node; +use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; + +/** + * Renders a directory node. + */ +class Directory extends Renderer +{ + /** + * @param DirectoryNode $node + * @param string $file + */ + public function render(DirectoryNode $node, $file) + { + $template = new \Text_Template($this->templatePath . 'directory.html', '{{', '}}'); + + $this->setCommonTemplateVariables($template, $node); + + $items = $this->renderItem($node, true); + + foreach ($node->getDirectories() as $item) { + $items .= $this->renderItem($item); + } + + foreach ($node->getFiles() as $item) { + $items .= $this->renderItem($item); + } + + $template->setVar( + [ + 'id' => $node->getId(), + 'items' => $items + ] + ); + + $template->renderTo($file); + } + + /** + * @param Node $node + * @param bool $total + * + * @return string + */ + protected function renderItem(Node $node, $total = false) + { + $data = [ + 'numClasses' => $node->getNumClassesAndTraits(), + 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), + 'numMethods' => $node->getNumMethods(), + 'numTestedMethods' => $node->getNumTestedMethods(), + 'linesExecutedPercent' => $node->getLineExecutedPercent(false), + 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), + 'numExecutedLines' => $node->getNumExecutedLines(), + 'numExecutableLines' => $node->getNumExecutableLines(), + 'testedMethodsPercent' => $node->getTestedMethodsPercent(false), + 'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(), + 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), + 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent() + ]; + + if ($total) { + $data['name'] = 'Total'; + } else { + if ($node instanceof DirectoryNode) { + $data['name'] = sprintf( + '%s', + $node->getName(), + $node->getName() + ); + + $data['icon'] = ' '; + } else { + $data['name'] = sprintf( + '%s', + $node->getName(), + $node->getName() + ); + + $data['icon'] = ' '; + } + } + + return $this->renderItemTemplate( + new \Text_Template($this->templatePath . 'directory_item.html', '{{', '}}'), + $data + ); + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php new file mode 100644 index 0000000000..5461c9e763 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php @@ -0,0 +1,551 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SebastianBergmann\CodeCoverage\Report\Html; + +use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\Util; + +/** + * Renders a file node. + */ +class File extends Renderer +{ + /** + * @var int + */ + private $htmlspecialcharsFlags; + + /** + * Constructor. + * + * @param string $templatePath + * @param string $generator + * @param string $date + * @param int $lowUpperBound + * @param int $highLowerBound + */ + public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound) + { + parent::__construct( + $templatePath, + $generator, + $date, + $lowUpperBound, + $highLowerBound + ); + + $this->htmlspecialcharsFlags = ENT_COMPAT; + + $this->htmlspecialcharsFlags = $this->htmlspecialcharsFlags | ENT_HTML401 | ENT_SUBSTITUTE; + } + + /** + * @param FileNode $node + * @param string $file + */ + public function render(FileNode $node, $file) + { + $template = new \Text_Template($this->templatePath . 'file.html', '{{', '}}'); + + $template->setVar( + [ + 'items' => $this->renderItems($node), + 'lines' => $this->renderSource($node) + ] + ); + + $this->setCommonTemplateVariables($template, $node); + + $template->renderTo($file); + } + + /** + * @param FileNode $node + * + * @return string + */ + protected function renderItems(FileNode $node) + { + $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); + + $methodItemTemplate = new \Text_Template( + $this->templatePath . 'method_item.html', + '{{', + '}}' + ); + + $items = $this->renderItemTemplate( + $template, + [ + 'name' => 'Total', + 'numClasses' => $node->getNumClassesAndTraits(), + 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), + 'numMethods' => $node->getNumMethods(), + 'numTestedMethods' => $node->getNumTestedMethods(), + 'linesExecutedPercent' => $node->getLineExecutedPercent(false), + 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), + 'numExecutedLines' => $node->getNumExecutedLines(), + 'numExecutableLines' => $node->getNumExecutableLines(), + 'testedMethodsPercent' => $node->getTestedMethodsPercent(false), + 'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(), + 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), + 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), + 'crap' => 'CRAP' + ] + ); + + $items .= $this->renderFunctionItems( + $node->getFunctions(), + $methodItemTemplate + ); + + $items .= $this->renderTraitOrClassItems( + $node->getTraits(), + $template, + $methodItemTemplate + ); + + $items .= $this->renderTraitOrClassItems( + $node->getClasses(), + $template, + $methodItemTemplate + ); + + return $items; + } + + /** + * @param array $items + * @param \Text_Template $template + * @param \Text_Template $methodItemTemplate + * + * @return string + */ + protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate) + { + if (empty($items)) { + return ''; + } + + $buffer = ''; + + foreach ($items as $name => $item) { + $numMethods = count($item['methods']); + $numTestedMethods = 0; + + foreach ($item['methods'] as $method) { + if ($method['executedLines'] == $method['executableLines']) { + $numTestedMethods++; + } + } + + if ($item['executableLines'] > 0) { + $numClasses = 1; + $numTestedClasses = $numTestedMethods == $numMethods ? 1 : 0; + $linesExecutedPercentAsString = Util::percent( + $item['executedLines'], + $item['executableLines'], + true + ); + } else { + $numClasses = 'n/a'; + $numTestedClasses = 'n/a'; + $linesExecutedPercentAsString = 'n/a'; + } + + $buffer .= $this->renderItemTemplate( + $template, + [ + 'name' => $name, + 'numClasses' => $numClasses, + 'numTestedClasses' => $numTestedClasses, + 'numMethods' => $numMethods, + 'numTestedMethods' => $numTestedMethods, + 'linesExecutedPercent' => Util::percent( + $item['executedLines'], + $item['executableLines'], + false + ), + 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, + 'numExecutedLines' => $item['executedLines'], + 'numExecutableLines' => $item['executableLines'], + 'testedMethodsPercent' => Util::percent( + $numTestedMethods, + $numMethods, + false + ), + 'testedMethodsPercentAsString' => Util::percent( + $numTestedMethods, + $numMethods, + true + ), + 'testedClassesPercent' => Util::percent( + $numTestedMethods == $numMethods ? 1 : 0, + 1, + false + ), + 'testedClassesPercentAsString' => Util::percent( + $numTestedMethods == $numMethods ? 1 : 0, + 1, + true + ), + 'crap' => $item['crap'] + ] + ); + + foreach ($item['methods'] as $method) { + $buffer .= $this->renderFunctionOrMethodItem( + $methodItemTemplate, + $method, + ' ' + ); + } + } + + return $buffer; + } + + /** + * @param array $functions + * @param \Text_Template $template + * + * @return string + */ + protected function renderFunctionItems(array $functions, \Text_Template $template) + { + if (empty($functions)) { + return ''; + } + + $buffer = ''; + + foreach ($functions as $function) { + $buffer .= $this->renderFunctionOrMethodItem( + $template, + $function + ); + } + + return $buffer; + } + + /** + * @param \Text_Template $template + * + * @return string + */ + protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, $indent = '') + { + $numTestedItems = $item['executedLines'] == $item['executableLines'] ? 1 : 0; + + return $this->renderItemTemplate( + $template, + [ + 'name' => sprintf( + '%s%s', + $indent, + $item['startLine'], + htmlspecialchars($item['signature']), + isset($item['functionName']) ? $item['functionName'] : $item['methodName'] + ), + 'numMethods' => 1, + 'numTestedMethods' => $numTestedItems, + 'linesExecutedPercent' => Util::percent( + $item['executedLines'], + $item['executableLines'], + false + ), + 'linesExecutedPercentAsString' => Util::percent( + $item['executedLines'], + $item['executableLines'], + true + ), + 'numExecutedLines' => $item['executedLines'], + 'numExecutableLines' => $item['executableLines'], + 'testedMethodsPercent' => Util::percent( + $numTestedItems, + 1, + false + ), + 'testedMethodsPercentAsString' => Util::percent( + $numTestedItems, + 1, + true + ), + 'crap' => $item['crap'] + ] + ); + } + + /** + * @param FileNode $node + * + * @return string + */ + protected function renderSource(FileNode $node) + { + $coverageData = $node->getCoverageData(); + $testData = $node->getTestData(); + $codeLines = $this->loadFile($node->getPath()); + $lines = ''; + $i = 1; + + foreach ($codeLines as $line) { + $trClass = ''; + $popoverContent = ''; + $popoverTitle = ''; + + if (array_key_exists($i, $coverageData)) { + $numTests = count($coverageData[$i]); + + if ($coverageData[$i] === null) { + $trClass = ' class="warning"'; + } elseif ($numTests == 0) { + $trClass = ' class="danger"'; + } else { + $lineCss = 'covered-by-large-tests'; + $popoverContent = '
      '; + + if ($numTests > 1) { + $popoverTitle = $numTests . ' tests cover line ' . $i; + } else { + $popoverTitle = '1 test covers line ' . $i; + } + + foreach ($coverageData[$i] as $test) { + if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { + $lineCss = 'covered-by-medium-tests'; + } elseif ($testData[$test]['size'] == 'small') { + $lineCss = 'covered-by-small-tests'; + } + + switch ($testData[$test]['status']) { + case 0: + switch ($testData[$test]['size']) { + case 'small': + $testCSS = ' class="covered-by-small-tests"'; + break; + + case 'medium': + $testCSS = ' class="covered-by-medium-tests"'; + break; + + default: + $testCSS = ' class="covered-by-large-tests"'; + break; + } + break; + + case 1: + case 2: + $testCSS = ' class="warning"'; + break; + + case 3: + $testCSS = ' class="danger"'; + break; + + case 4: + $testCSS = ' class="danger"'; + break; + + default: + $testCSS = ''; + } + + $popoverContent .= sprintf( + '%s', + $testCSS, + htmlspecialchars($test) + ); + } + + $popoverContent .= '
    '; + $trClass = ' class="' . $lineCss . ' popin"'; + } + } + + if (!empty($popoverTitle)) { + $popover = sprintf( + ' data-title="%s" data-content="%s" data-placement="bottom" data-html="true"', + $popoverTitle, + htmlspecialchars($popoverContent) + ); + } else { + $popover = ''; + } + + $lines .= sprintf( + ' %s' . "\n", + $trClass, + $popover, + $i, + $i, + $i, + $line + ); + + $i++; + } + + return $lines; + } + + /** + * @param string $file + * + * @return array + */ + protected function loadFile($file) + { + $buffer = file_get_contents($file); + $tokens = token_get_all($buffer); + $result = ['']; + $i = 0; + $stringFlag = false; + $fileEndsWithNewLine = substr($buffer, -1) == "\n"; + + unset($buffer); + + foreach ($tokens as $j => $token) { + if (is_string($token)) { + if ($token === '"' && $tokens[$j - 1] !== '\\') { + $result[$i] .= sprintf( + '%s', + htmlspecialchars($token) + ); + + $stringFlag = !$stringFlag; + } else { + $result[$i] .= sprintf( + '%s', + htmlspecialchars($token) + ); + } + + continue; + } + + list($token, $value) = $token; + + $value = str_replace( + ["\t", ' '], + ['    ', ' '], + htmlspecialchars($value, $this->htmlspecialcharsFlags) + ); + + if ($value === "\n") { + $result[++$i] = ''; + } else { + $lines = explode("\n", $value); + + foreach ($lines as $jj => $line) { + $line = trim($line); + + if ($line !== '') { + if ($stringFlag) { + $colour = 'string'; + } else { + switch ($token) { + case T_INLINE_HTML: + $colour = 'html'; + break; + + case T_COMMENT: + case T_DOC_COMMENT: + $colour = 'comment'; + break; + + case T_ABSTRACT: + case T_ARRAY: + case T_AS: + case T_BREAK: + case T_CALLABLE: + case T_CASE: + case T_CATCH: + case T_CLASS: + case T_CLONE: + case T_CONTINUE: + case T_DEFAULT: + case T_ECHO: + case T_ELSE: + case T_ELSEIF: + case T_EMPTY: + case T_ENDDECLARE: + case T_ENDFOR: + case T_ENDFOREACH: + case T_ENDIF: + case T_ENDSWITCH: + case T_ENDWHILE: + case T_EXIT: + case T_EXTENDS: + case T_FINAL: + case T_FINALLY: + case T_FOREACH: + case T_FUNCTION: + case T_GLOBAL: + case T_IF: + case T_IMPLEMENTS: + case T_INCLUDE: + case T_INCLUDE_ONCE: + case T_INSTANCEOF: + case T_INSTEADOF: + case T_INTERFACE: + case T_ISSET: + case T_LOGICAL_AND: + case T_LOGICAL_OR: + case T_LOGICAL_XOR: + case T_NAMESPACE: + case T_NEW: + case T_PRIVATE: + case T_PROTECTED: + case T_PUBLIC: + case T_REQUIRE: + case T_REQUIRE_ONCE: + case T_RETURN: + case T_STATIC: + case T_THROW: + case T_TRAIT: + case T_TRY: + case T_UNSET: + case T_USE: + case T_VAR: + case T_WHILE: + case T_YIELD: + $colour = 'keyword'; + break; + + default: + $colour = 'default'; + } + } + + $result[$i] .= sprintf( + '%s', + $colour, + $line + ); + } + + if (isset($lines[$jj + 1])) { + $result[++$i] = ''; + } + } + } + } + + if ($fileEndsWithNewLine) { + unset($result[count($result)-1]); + } + + return $result; + } +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar.html.dist new file mode 100644 index 0000000000..5a09c354dc --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/coverage_bar.html.dist @@ -0,0 +1,5 @@ +
    +
    + {{percent}}% covered ({{level}}) +
    +
    diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.css new file mode 100644 index 0000000000..ed3905e0e0 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.css new file mode 100644 index 0000000000..7a6f7fe90c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/nv.d3.min.css @@ -0,0 +1 @@ +.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css new file mode 100644 index 0000000000..824fb3171d --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/css/style.css @@ -0,0 +1,122 @@ +body { + padding-top: 10px; +} + +.popover { + max-width: none; +} + +.glyphicon { + margin-right:.25em; +} + +.table-bordered>thead>tr>td { + border-bottom-width: 1px; +} + +.table tbody>tr>td, .table thead>tr>td { + padding-top: 3px; + padding-bottom: 3px; +} + +.table-condensed tbody>tr>td { + padding-top: 0; + padding-bottom: 0; +} + +.table .progress { + margin-bottom: inherit; +} + +.table-borderless th, .table-borderless td { + border: 0 !important; +} + +.table tbody tr.covered-by-large-tests, li.covered-by-large-tests, tr.success, td.success, li.success, span.success { + background-color: #dff0d8; +} + +.table tbody tr.covered-by-medium-tests, li.covered-by-medium-tests { + background-color: #c3e3b5; +} + +.table tbody tr.covered-by-small-tests, li.covered-by-small-tests { + background-color: #99cb84; +} + +.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { + background-color: #f2dede; +} + +.table tbody td.warning, li.warning, span.warning { + background-color: #fcf8e3; +} + +.table tbody td.info { + background-color: #d9edf7; +} + +td.big { + width: 117px; +} + +td.small { +} + +td.codeLine { + font-family: monospace; + white-space: pre; +} + +td span.comment { + color: #888a85; +} + +td span.default { + color: #2e3436; +} + +td span.html { + color: #888a85; +} + +td span.keyword { + color: #2e3436; + font-weight: bold; +} + +pre span.string { + color: #2e3436; +} + +span.success, span.warning, span.danger { + margin-right: 2px; + padding-left: 10px; + padding-right: 10px; + text-align: center; +} + +#classCoverageDistribution, #classComplexity { + height: 200px; + width: 475px; +} + +#toplink { + position: fixed; + left: 5px; + bottom: 5px; + outline: 0; +} + +svg text { + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + color: #666; + fill: #666; +} + +.scrollbox { + height:245px; + overflow-x:hidden; + overflow-y:scroll; +} diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.dist new file mode 100644 index 0000000000..8bdf04d837 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard.html.dist @@ -0,0 +1,284 @@ + + + + + Dashboard for {{full_path}} + + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +
    +
    +

    Coverage Distribution

    +
    + +
    +
    +
    +

    Complexity

    +
    + +
    +
    +
    +
    +
    +

    Insufficient Coverage

    +
    + + + + + + + + +{{insufficient_coverage_classes}} + +
    ClassCoverage
    +
    +
    +
    +

    Project Risks

    +
    + + + + + + + + +{{project_risks_classes}} + +
    ClassCRAP
    +
    +
    +
    +
    +
    +

    Methods

    +
    +
    +
    +
    +

    Coverage Distribution

    +
    + +
    +
    +
    +

    Complexity

    +
    + +
    +
    +
    +
    +
    +

    Insufficient Coverage

    +
    + + + + + + + + +{{insufficient_coverage_methods}} + +
    MethodCoverage
    +
    +
    +
    +

    Project Risks

    +
    + + + + + + + + +{{project_risks_methods}} + +
    MethodCRAP
    +
    +
    +
    + +
    + + + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist new file mode 100644 index 0000000000..29fbf23ead --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory.html.dist @@ -0,0 +1,61 @@ + + + + + Code Coverage for {{full_path}} + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + +{{items}} + +
     
    Code Coverage
     
    Lines
    Functions and Methods
    Classes and Traits
    +
    +
    +

    Legend

    +

    + Low: 0% to {{low_upper_bound}}% + Medium: {{low_upper_bound}}% to {{high_lower_bound}}% + High: {{high_lower_bound}}% to 100% +

    +

    + Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

    +
    +
    + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.dist new file mode 100644 index 0000000000..78dbb3565c --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/directory_item.html.dist @@ -0,0 +1,13 @@ + + {{icon}}{{name}} + {{lines_bar}} +
    {{lines_executed_percent}}
    +
    {{lines_number}}
    + {{methods_bar}} +
    {{methods_tested_percent}}
    +
    {{methods_number}}
    + {{classes_bar}} +
    {{classes_tested_percent}}
    +
    {{classes_number}}
    + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist new file mode 100644 index 0000000000..8c42d4e818 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist @@ -0,0 +1,90 @@ + + + + + Code Coverage for {{full_path}} + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + +{{items}} + +
     
    Code Coverage
     
    Classes and Traits
    Functions and Methods
    Lines
    + + +{{lines}} + +
    +
    +
    +

    Legend

    +

    + Executed + Not Executed + Dead Code +

    +

    + Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

    + +
    +
    + + + + + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.dist b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.dist new file mode 100644 index 0000000000..756fdd69b1 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_item.html.dist @@ -0,0 +1,14 @@ + + {{name}} + {{classes_bar}} +
    {{classes_tested_percent}}
    +
    {{classes_number}}
    + {{methods_bar}} +
    {{methods_tested_percent}}
    +
    {{methods_number}}
    + {{crap}} + {{lines_bar}} +
    {{lines_executed_percent}}
    +
    {{lines_number}}
    + + diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.eot b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000..b93a4953ff Binary files /dev/null and b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.eot differ diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.svg b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000000..94fb5490a2 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.ttf b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000..1413fc609a Binary files /dev/null and b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.ttf differ diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.woff b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000..9e612858f8 Binary files /dev/null and b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.woff differ diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.woff2 b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000..64539b54c3 Binary files /dev/null and b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js new file mode 100644 index 0000000000..9bcd2fccae --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js new file mode 100644 index 0000000000..166487309a --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ +r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; +if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file diff --git a/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/holder.min.js b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/holder.min.js new file mode 100644 index 0000000000..6bfc844ba5 --- /dev/null +++ b/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/holder.min.js @@ -0,0 +1,12 @@ +/*! + +Holder - client side image placeholders +Version 2.7.1+6hydf +© 2015 Ivan Malopinsky - http://imsky.co + +Site: http://holderjs.com +Issues: https://github.com/imsky/holder/issues +License: http://opensource.org/licenses/MIT + +*/ +!function(a){if(a.document){var b=a.document;b.querySelectorAll||(b.querySelectorAll=function(c){var d,e=b.createElement("style"),f=[];for(b.documentElement.firstChild.appendChild(e),b._qsa=[],e.styleSheet.cssText=c+"{x-qsa:expression(document._qsa && document._qsa.push(this))}",a.scrollBy(0,0),e.parentNode.removeChild(e);b._qsa.length;)d=b._qsa.shift(),d.style.removeAttribute("x-qsa"),f.push(d);return b._qsa=null,f}),b.querySelector||(b.querySelector=function(a){var c=b.querySelectorAll(a);return c.length?c[0]:null}),b.getElementsByClassName||(b.getElementsByClassName=function(a){return a=String(a).replace(/^|\s+/g,"."),b.querySelectorAll(a)}),Object.keys||(Object.keys=function(a){if(a!==Object(a))throw TypeError("Object.keys called on non-object");var b,c=[];for(b in a)Object.prototype.hasOwnProperty.call(a,b)&&c.push(b);return c}),function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";a.atob=a.atob||function(a){a=String(a);var c,d=0,e=[],f=0,g=0;if(a=a.replace(/\s/g,""),a.length%4===0&&(a=a.replace(/=+$/,"")),a.length%4===1)throw Error("InvalidCharacterError");if(/[^+/0-9A-Za-z]/.test(a))throw Error("InvalidCharacterError");for(;d>16&255)),e.push(String.fromCharCode(f>>8&255)),e.push(String.fromCharCode(255&f)),g=0,f=0),d+=1;return 12===g?(f>>=4,e.push(String.fromCharCode(255&f))):18===g&&(f>>=2,e.push(String.fromCharCode(f>>8&255)),e.push(String.fromCharCode(255&f))),e.join("")},a.btoa=a.btoa||function(a){a=String(a);var c,d,e,f,g,h,i,j=0,k=[];if(/[^\x00-\xFF]/.test(a))throw Error("InvalidCharacterError");for(;j>2,g=(3&c)<<4|d>>4,h=(15&d)<<2|e>>6,i=63&e,j===a.length+2?(h=64,i=64):j===a.length+1&&(i=64),k.push(b.charAt(f),b.charAt(g),b.charAt(h),b.charAt(i));return k.join("")}}(a),Object.prototype.hasOwnProperty||(Object.prototype.hasOwnProperty=function(a){var b=this.__proto__||this.constructor.prototype;return a in this&&(!(a in b)||b[a]!==this[a])}),function(){if("performance"in a==!1&&(a.performance={}),Date.now=Date.now||function(){return(new Date).getTime()},"now"in a.performance==!1){var b=Date.now();performance.timing&&performance.timing.navigationStart&&(b=performance.timing.navigationStart),a.performance.now=function(){return Date.now()-b}}}(),a.requestAnimationFrame||(a.webkitRequestAnimationFrame?!function(a){a.requestAnimationFrame=function(b){return webkitRequestAnimationFrame(function(){b(a.performance.now())})},a.cancelAnimationFrame=webkitCancelAnimationFrame}(a):a.mozRequestAnimationFrame?!function(a){a.requestAnimationFrame=function(b){return mozRequestAnimationFrame(function(){b(a.performance.now())})},a.cancelAnimationFrame=mozCancelAnimationFrame}(a):!function(a){a.requestAnimationFrame=function(b){return a.setTimeout(b,1e3/60)},a.cancelAnimationFrame=a.clearTimeout}(a))}}(this),function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):"object"==typeof exports?exports.Holder=b():a.Holder=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){(function(b){function d(a,b,c,d){var f=e(c.substr(c.lastIndexOf(a.domain)),a);f&&h({mode:null,el:d,flags:f,engineSettings:b})}function e(a,b){var c={theme:B(J.settings.themes.gray,null),stylesheets:b.stylesheets,instanceOptions:b};return a.match(/([\d]+p?)x([\d]+p?)(?:\?|$)/)?f(a,c):g(a,c)}function f(a,b){var c=a.split("?"),d=c[0].split("/");b.holderURL=a;var e=d[1],f=e.match(/([\d]+p?)x([\d]+p?)/);if(!f)return!1;if(b.fluid=-1!==e.indexOf("p"),b.dimensions={width:f[1].replace("p","%"),height:f[2].replace("p","%")},2===c.length){var g=A.parse(c[1]);if(g.bg&&(b.theme.background=(-1===g.bg.indexOf("#")?"#":"")+g.bg),g.fg&&(b.theme.foreground=(-1===g.fg.indexOf("#")?"#":"")+g.fg),g.theme&&b.instanceOptions.themes.hasOwnProperty(g.theme)&&(b.theme=B(b.instanceOptions.themes[g.theme],null)),g.text&&(b.text=g.text),g.textmode&&(b.textmode=g.textmode),g.size&&(b.size=g.size),g.font&&(b.font=g.font),g.align&&(b.align=g.align),b.nowrap=z.truthy(g.nowrap),b.auto=z.truthy(g.auto),z.truthy(g.random)){J.vars.cache.themeKeys=J.vars.cache.themeKeys||Object.keys(b.instanceOptions.themes);var h=J.vars.cache.themeKeys[0|Math.random()*J.vars.cache.themeKeys.length];b.theme=B(b.instanceOptions.themes[h],null)}}return b}function g(a,b){var c=!1,d=String.fromCharCode(11),e=a.replace(/([^\\])\//g,"$1"+d).split(d),f=/%[0-9a-f]{2}/gi,g=b.instanceOptions;b.holderURL=[];for(var h=e.length,i=0;h>i;i++){var j=e[i];if(j.match(f))try{j=decodeURIComponent(j)}catch(k){j=e[i]}var l=!1;if(J.flags.dimensions.match(j))c=!0,b.dimensions=J.flags.dimensions.output(j),l=!0;else if(J.flags.fluid.match(j))c=!0,b.dimensions=J.flags.fluid.output(j),b.fluid=!0,l=!0;else if(J.flags.textmode.match(j))b.textmode=J.flags.textmode.output(j),l=!0;else if(J.flags.colors.match(j)){var m=J.flags.colors.output(j);b.theme=B(b.theme,m),l=!0}else if(g.themes[j])g.themes.hasOwnProperty(j)&&(b.theme=B(g.themes[j],null)),l=!0;else if(J.flags.font.match(j))b.font=J.flags.font.output(j),l=!0;else if(J.flags.auto.match(j))b.auto=!0,l=!0;else if(J.flags.text.match(j))b.text=J.flags.text.output(j),l=!0;else if(J.flags.size.match(j))b.size=J.flags.size.output(j),l=!0;else if(J.flags.random.match(j)){null==J.vars.cache.themeKeys&&(J.vars.cache.themeKeys=Object.keys(g.themes));var n=J.vars.cache.themeKeys[0|Math.random()*J.vars.cache.themeKeys.length];b.theme=B(g.themes[n],null),l=!0}l&&b.holderURL.push(j)}return b.holderURL.unshift(g.domain),b.holderURL=b.holderURL.join("/"),c?b:!1}function h(a){var b=a.mode,c=a.el,d=a.flags,e=a.engineSettings,f=d.dimensions,g=d.theme,h=f.width+"x"+f.height;if(b=null==b?d.fluid?"fluid":"image":b,null!=d.text&&(g.text=d.text,"object"===c.nodeName.toLowerCase())){for(var j=g.text.split("\\n"),k=0;k1){var n,o=0,p=0,q=0;j=new e.Group("line"+q),("left"===a.align||"right"===a.align)&&(m=a.width*(1-2*(1-J.setup.lineWrapRatio)));for(var r=0;r=m||t===!0)&&(b(g,j,o,g.properties.leading),g.add(j),o=0,p+=g.properties.leading,q+=1,j=new e.Group("line"+q),j.y=p),t!==!0&&(i.moveTo(o,0),o+=h.spaceWidth+s.width,j.add(i))}if(b(g,j,o,g.properties.leading),g.add(j),"left"===a.align)g.moveTo(a.width-l,null,null);else if("right"===a.align){for(n in g.children)j=g.children[n],j.moveTo(a.width-j.width,null,null);g.moveTo(0-(a.width-l),null,null)}else{for(n in g.children)j=g.children[n],j.moveTo((g.width-j.width)/2,null,null);g.moveTo((a.width-g.width)/2,null,null)}g.moveTo(null,(a.height-g.height)/2,null),(a.height-g.height)/2<0&&g.moveTo(null,0,null)}else i=new e.Text(a.text),j=new e.Group("line0"),j.add(i),g.add(j),"left"===a.align?g.moveTo(a.width-l,null,null):"right"===a.align?g.moveTo(0-(a.width-l),null,null):g.moveTo((a.width-h.boundingBox.width)/2,null,null),g.moveTo(null,(a.height-h.boundingBox.height)/2,null);return d}function k(a,b,c){var d=parseInt(a,10),e=parseInt(b,10),f=Math.max(d,e),g=Math.min(d,e),h=.8*Math.min(g,f*J.defaults.scale);return Math.round(Math.max(c,h))}function l(a){var b;b=null==a||null==a.nodeType?J.vars.resizableImages:[a];for(var c=0,d=b.length;d>c;c++){var e=b[c];if(e.holderData){var f=e.holderData.flags,g=D(e);if(g){if(!e.holderData.resizeUpdate)continue;if(f.fluid&&f.auto){var h=e.holderData.fluidConfig;switch(h.mode){case"width":g.height=g.width/h.ratio;break;case"height":g.width=g.height*h.ratio}}var j={mode:"image",holderSettings:{dimensions:g,theme:f.theme,flags:f},el:e,engineSettings:e.holderData.engineSettings};"exact"==f.textmode&&(f.exactDimensions=g,j.holderSettings.dimensions=f.dimensions),i(j)}else p(e)}}}function m(a){if(a.holderData){var b=D(a);if(b){var c=a.holderData.flags,d={fluidHeight:"%"==c.dimensions.height.slice(-1),fluidWidth:"%"==c.dimensions.width.slice(-1),mode:null,initialDimensions:b};d.fluidWidth&&!d.fluidHeight?(d.mode="width",d.ratio=d.initialDimensions.width/parseFloat(c.dimensions.height)):!d.fluidWidth&&d.fluidHeight&&(d.mode="height",d.ratio=parseFloat(c.dimensions.width)/d.initialDimensions.height),a.holderData.fluidConfig=d}else p(a)}}function n(){for(var a,c=[],d=Object.keys(J.vars.invisibleImages),e=0,f=d.length;f>e;e++)a=J.vars.invisibleImages[d[e]],D(a)&&"img"==a.nodeName.toLowerCase()&&(c.push(a),delete J.vars.invisibleImages[d[e]]);c.length&&I.run({images:c}),b.requestAnimationFrame(n)}function o(){J.vars.visibilityCheckStarted||(b.requestAnimationFrame(n),J.vars.visibilityCheckStarted=!0)}function p(a){a.holderData.invisibleId||(J.vars.invisibleId+=1,J.vars.invisibleImages["i"+J.vars.invisibleId]=a,a.holderData.invisibleId=J.vars.invisibleId)}function q(a,b){return null==b?document.createElement(a):document.createElementNS(b,a)}function r(a,b){for(var c in b)a.setAttribute(c,b[c])}function s(a,b,c){var d,e;null==a?(a=q("svg",E),d=q("defs",E),e=q("style",E),r(e,{type:"text/css"}),d.appendChild(e),a.appendChild(d)):e=a.querySelector("style"),a.webkitMatchesSelector&&a.setAttribute("xmlns",E);for(var f=0;f=0;h--){var i=g.createProcessingInstruction("xml-stylesheet",'href="'+f[h]+'" rel="stylesheet"');g.insertBefore(i,g.firstChild)}g.removeChild(g.documentElement),e=d.serializeToString(g)}var j=d.serializeToString(a);return j=j.replace(/\&(\#[0-9]{2,}\;)/g,"&$1"),e+j}}function u(){return b.DOMParser?(new DOMParser).parseFromString("","application/xml"):void 0}function v(a){J.vars.debounceTimer||a.call(this),J.vars.debounceTimer&&b.clearTimeout(J.vars.debounceTimer),J.vars.debounceTimer=b.setTimeout(function(){J.vars.debounceTimer=null,a.call(this)},J.setup.debounce)}function w(){v(function(){l(null)})}var x=c(1),y=c(2),z=c(3),A=c(4),B=z.extend,C=z.getNodeArray,D=z.dimensionCheck,E="http://www.w3.org/2000/svg",F=8,G="2.7.1",H="\nCreated with Holder.js "+G+".\nLearn more at http://holderjs.com\n(c) 2012-2015 Ivan Malopinsky - http://imsky.co\n",I={version:G,addTheme:function(a,b){return null!=a&&null!=b&&(J.settings.themes[a]=b),delete J.vars.cache.themeKeys,this},addImage:function(a,b){var c=document.querySelectorAll(b);if(c.length)for(var d=0,e=c.length;e>d;d++){var f=q("img"),g={};g[J.vars.dataAttr]=a,r(f,g),c[d].appendChild(f)}return this},setResizeUpdate:function(a,b){a.holderData&&(a.holderData.resizeUpdate=!!b,a.holderData.resizeUpdate&&l(a))},run:function(a){a=a||{};var c={},f=B(J.settings,a);J.vars.preempted=!0,J.vars.dataAttr=f.dataAttr||J.vars.dataAttr,c.renderer=f.renderer?f.renderer:J.setup.renderer,-1===J.setup.renderers.join(",").indexOf(c.renderer)&&(c.renderer=J.setup.supportsSVG?"svg":J.setup.supportsCanvas?"canvas":"html");var g=C(f.images),i=C(f.bgnodes),j=C(f.stylenodes),k=C(f.objects);c.stylesheets=[],c.svgXMLStylesheet=!0,c.noFontFallback=f.noFontFallback?f.noFontFallback:!1;for(var l=0;l1){c.nodeValue="";for(var u=0;u=0?b:1)}function f(a){v?e(a):w.push(a)}null==document.readyState&&document.addEventListener&&(document.addEventListener("DOMContentLoaded",function y(){document.removeEventListener("DOMContentLoaded",y,!1),document.readyState="complete"},!1),document.readyState="loading");var g=a.document,h=g.documentElement,i="load",j=!1,k="on"+i,l="complete",m="readyState",n="attachEvent",o="detachEvent",p="addEventListener",q="DOMContentLoaded",r="onreadystatechange",s="removeEventListener",t=p in g,u=j,v=j,w=[];if(g[m]===l)e(b);else if(t)g[p](q,c,j),a[p](i,c,j);else{g[n](r,c),a[n](k,c);try{u=null==a.frameElement&&h}catch(x){}u&&u.doScroll&&!function z(){if(!v){try{u.doScroll("left")}catch(a){return e(z,50)}d(),b()}}()}return f.version="1.4.0",f.isReady=function(){return v},f}a.exports="undefined"!=typeof window&&b(window)},function(a,b,c){var d=c(5),e=function(a){function b(a,b){for(var c in b)a[c]=b[c];return a}var c=1,e=d.defclass({constructor:function(a){c++,this.parent=null,this.children={},this.id=c,this.name="n"+c,null!=a&&(this.name=a),this.x=0,this.y=0,this.z=0,this.width=0,this.height=0},resize:function(a,b){null!=a&&(this.width=a),null!=b&&(this.height=b)},moveTo:function(a,b,c){this.x=null!=a?a:this.x,this.y=null!=b?b:this.y,this.z=null!=c?c:this.z},add:function(a){var b=a.name;if(null!=this.children[b])throw"SceneGraph: child with that name already exists: "+b;this.children[b]=a,a.parent=this}}),f=d(e,function(b){this.constructor=function(){b.constructor.call(this,"root"),this.properties=a}}),g=d(e,function(a){function c(c,d){if(a.constructor.call(this,c),this.properties={fill:"#000"},null!=d)b(this.properties,d);else if(null!=c&&"string"!=typeof c)throw"SceneGraph: invalid node name"}this.Group=d.extend(this,{constructor:c,type:"group"}),this.Rect=d.extend(this,{constructor:c,type:"rect"}),this.Text=d.extend(this,{constructor:function(a){c.call(this),this.properties.text=a},type:"text"})}),h=new f;return this.Shape=g,this.root=h,this};a.exports=e},function(a,b){(function(a){b.extend=function(a,b){var c={};for(var d in a)a.hasOwnProperty(d)&&(c[d]=a[d]);if(null!=b)for(var e in b)b.hasOwnProperty(e)&&(c[e]=b[e]);return c},b.cssProps=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+":"+a[c]);return b.join(";")},b.encodeHtmlEntity=function(a){for(var b=[],c=0,d=a.length-1;d>=0;d--)c=a.charCodeAt(d),b.unshift(c>128?["&#",c,";"].join(""):a[d]);return b.join("")},b.getNodeArray=function(b){var c=null;return"string"==typeof b?c=document.querySelectorAll(b):a.NodeList&&b instanceof a.NodeList?c=b:a.Node&&b instanceof a.Node?c=[b]:a.HTMLCollection&&b instanceof a.HTMLCollection?c=b:b instanceof Array?c=b:null===b&&(c=[]),c},b.imageExists=function(a,b){var c=new Image;c.onerror=function(){b.call(this,!1)},c.onload=function(){b.call(this,!0)},c.src=a},b.decodeHtmlEntity=function(a){return a.replace(/&#(\d+);/g,function(a,b){return String.fromCharCode(b)})},b.dimensionCheck=function(a){var b={height:a.clientHeight,width:a.clientWidth};return b.height&&b.width?b:!1},b.truthy=function(a){return"string"==typeof a?"true"===a||"yes"===a||"1"===a||"on"===a||"✓"===a:!!a}}).call(b,function(){return this}())},function(a,b,c){var d=encodeURIComponent,e=decodeURIComponent,f=c(6),g=c(7),h=/(\w+)\[(\d+)\]/,i=/\w+\.\w+/;b.parse=function(a){if("string"!=typeof a)return{};if(a=f(a),""===a)return{};"?"===a.charAt(0)&&(a=a.slice(1));for(var b={},c=a.split("&"),d=0;d