diff --git a/README.md b/README.md index 31acd9a..b2ac497 100755 --- a/README.md +++ b/README.md @@ -1,69 +1,7 @@ -# Cloudinary Extension +# Cloudinary Magento Extensions -Cloudinary Magento extension. +This repository contains packages to support Cloudinary on Magento platforms. -## IMPORTANT INFORMATION - -Currently the extension doesn't cope well with changing clouds when the extension is in use because of the image synchronisation between Magento and Cloudinary. -Changing clouds will cause Magento to be unaware that the images that were already synchronised are now not available in the newly specified cloud. If the records for the synchronisation are removed, then it is possible to run the migration process for the new cloud, but if the configuration ever reverts to the previous cloud, there will be no record of the previously synchronised images, and, in addition to having to reset the synchronisation records to be able to run the migration again, the images will be re-uploaded and exist duplicated on cloudinary. - - -## Functionality Overview - -At the current time (December 2014), the extension is replacing the basic Magento image functionality and providing it via Cloudinary. This allows for on-the-fly image optimisation for specific web clients, and CDN-like capabilities, thus moving the traffic away from the servers as well as serving it from the Cloudinary high performing and geo-location aware network. - -### Image Upload - -From an admin perspective, Image upload is in no way different from the standard Magento image upload. The only change is that when the extension is enabled, images are uploaded to Cloudinary. - -### Image Display - -When the extension is enabled and the image is available in Cloudinary, the images served from the Cloudinary network rather than from the local infrastructure. From the user's perspective, there's no difference in behaviour other than the potential performance gains. - -### Credentials and cloud configuration - -The `key`, `secret` and `cloud` configuration are all available under the `System->Configuration` menu option, in the `Services` group under the name `Cloudinary`. - -### Image Migration - -The Cloudinary extension provides functionality to trigger the upload of pre-existing images to Cloudinary. This process is throttled to prevent network flooding, and is controlled manually to allow for store admins to choose when it should happen. -To start the migration process go to the `Cloudinary->Manage` menu in Magento's admin panel, and press the `Start Migration` button, note that if there are no images to migrate, the button will be greyed out. Pressing the `Start Migration` button (when it's not greyed out), will trigger the migration process and show the migration progress. When the migration finished, the `Start Migration` button will become greyed out. -It's possible to pause the migration process by pressing the `Stop Migration` button. This will allow you to continue the migration process later. -Images become available via Cloudinary as soon as they've been uploaded, so stopping the migration process still allows the site to benefit from Cloudinary for the images that were already uploaded. - -### Enabling/Disabling the extension - -The extension can be enabled and disabled at will. To disable the extension, go to the `Cloudinary->Manage` menu, and press the `Enable Cloudinary` \ `Disable Cloudinary` button. Keep in mind that when the extension is disabled, no images will be served from Cloudinary nor will new images be uploaded to Cloudinary. -If the extension is disabled at any point, it's advisable to start the migration process after enabling it. The `Start Migration` will be greyed out, if there are no images to migrate. - - -## Enabling/Disabling -The enable/disable button in the Cloudinary admin section determines whether the application will request its media from the remote filesystem (Cloudinary) or from the local server. Since the media will end up being store both locally and remotely, the extension can be enabled/disabled without a major impact on the systems operation. It can be done during/before/after migration. - -For example, when extension is *enabled*: -- If the image has already been uploaded to Cloudinary, the system will fetch the image from Cloudinary. -- If the has not yet been uploaded, the system will fetch it locally - -When extension is *disabled* -- The system will always fetch the image locally, regardless of whether it has been uploaded to Cloudinary or no. - -## Known Issues - -- When the migration is started, all existing media will gradually be uploaded to cloudinary. If the extension cannot upload an image (e.g. its missing, corrupted or is rejected by the remote service) it will *not* mark the image as having been migrated (syncrhonized) and will log an error message to system.log, the said image will thus not be removed from the queue of images to migrate and the migration will never complete. It is up to the *Integrator* to be aware of images that could not be uploaded and to decide if they should be deleted from the local database. The migration will only be marked as completed when all of the images in the media gallery have been successfully uploaded. - -## Running Gherkin Features -- Start phantomjs on the VM: -``` -phantomjs --webdriver 4444 --load-images=no & -``` -- Make sure you are in the cloudinary directory: -``` -cd /vagrant/vendor/inviqa/cloudinary -``` -- Run Behat -``` -bin/behat -fprogress -``` - -## API Version -This module currently uses version 1.1.* of the Cloudinary Api +- Magento 1 Cloudinary module +- Magento 2 Cloudinary module +- Shared core library used by both Magento modules. diff --git a/LICENSE b/core/.cp-remote-settings.yml similarity index 100% rename from LICENSE rename to core/.cp-remote-settings.yml diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..cc8b12c --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,3 @@ +bin/ +vendor/ +composer.phar diff --git a/INSTALL.md b/core/INSTALL.md old mode 100755 new mode 100644 similarity index 100% rename from INSTALL.md rename to core/INSTALL.md diff --git a/core/LICENSE b/core/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..4dcdb3a --- /dev/null +++ b/core/README.md @@ -0,0 +1 @@ +# cloudinary-core diff --git a/core/behat.yml b/core/behat.yml new file mode 100644 index 0000000..286893d --- /dev/null +++ b/core/behat.yml @@ -0,0 +1,27 @@ +default: + suites: + domain: + filters: { tags: '~@not-automated&&~@ui' } + contexts: + - Domain\DomainContext + - Domain\TransformationContext + - Domain\DeleteImageDomainContext + - Domain\ConfigurationContext + + extensions: + SensioLabs\Behat\PageObjectExtension: ~ + Behat\MinkExtension\ServiceContainer\MinkExtension: + base_url: 'http://cloudinary-test-environment.dev/' + goutte: + guzzle_parameters: + curl.options: + CURLOPT_SSL_VERIFYPEER: false + CURLOPT_CERTINFO: false + CURLOPT_TIMEOUT: 120 + ssl.certificate_authority: false + selenium2: + wd_host: http://localhost:4444/wd/hub + browser: phantomjs +# command to open the failing html pages: + show_cmd: echo '%s' + show_tmp_dir: /vagrant diff --git a/core/composer.json b/core/composer.json new file mode 100644 index 0000000..2de6832 --- /dev/null +++ b/core/composer.json @@ -0,0 +1,31 @@ +{ + "name": "inviqa/cloudinary-core", + "type": "cloudinary-core", + "license": "proprietary", + "description": "Cloudinary Core.", + "require": { + "php": ">=5.4.0", + "cloudinary/cloudinary_php": "~1.6.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.4.0", + "sensiolabs/behat-page-object-extension": "*@dev", + "behat/mink-selenium2-driver": "*", + "behat/mink-goutte-driver": "^1.0", + "squizlabs/php_codesniffer": "1.*", + "phpunit/phpunit": "3.7.*", + "mayflower/php-codebrowser": "^1.1", + "bossa/phpspec2-expect": "1.0.3" + }, + "config": { + "bin-dir": "bin" + }, + "autoload": { + "psr-0": { + "": [ + "features/bootstrap", + "lib" + ] + } + } +} diff --git a/core/composer.lock b/core/composer.lock new file mode 100644 index 0000000..02f68fd --- /dev/null +++ b/core/composer.lock @@ -0,0 +1,3361 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "8bf037e0a1e683b613e9e9bf9a1e8530", + "content-hash": "3882e107d8575e5cf28c9cf036fb347a", + "packages": [ + { + "name": "cloudinary/cloudinary_php", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/cloudinary/cloudinary_php.git", + "reference": "8b89be228b39bcdb36d5e642e9796c756760737e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cloudinary/cloudinary_php/zipball/8b89be228b39bcdb36d5e642e9796c756760737e", + "reference": "8b89be228b39bcdb36d5e642e9796c756760737e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.7.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cloudinary", + "homepage": "https://github.com/cloudinary/cloudinary_php/graphs/contributors" + } + ], + "description": "Cloudinary PHP SDK", + "homepage": "https://github.com/cloudinary/cloudinary_php", + "keywords": [ + "cdn", + "cloud", + "cloudinary", + "image management", + "sdk" + ], + "time": "2017-02-23 01:10:18" + } + ], + "packages-dev": [ + { + "name": "behat/behat", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "15a3a1857457eaa29cdf41564a5e421effb09526" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/15a3a1857457eaa29cdf41564a5e421effb09526", + "reference": "15a3a1857457eaa29cdf41564a5e421effb09526", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.4.4", + "behat/transliterator": "~1.0", + "container-interop/container-interop": "^1.1", + "ext-mbstring": "*", + "php": ">=5.3.3", + "symfony/class-loader": "~2.1||~3.0", + "symfony/config": "~2.3||~3.0", + "symfony/console": "~2.5||~3.0", + "symfony/dependency-injection": "~2.1||~3.0", + "symfony/event-dispatcher": "~2.1||~3.0", + "symfony/translation": "~2.3||~3.0", + "symfony/yaml": "~2.1||~3.0" + }, + "require-dev": { + "herrera-io/box": "~1.6.1", + "phpunit/phpunit": "~4.5", + "symfony/process": "~2.5|~3.0" + }, + "suggest": { + "behat/mink-extension": "for integration with Mink testing framework", + "behat/symfony2-extension": "for integration with Symfony2 web framework", + "behat/yii-extension": "for integration with Yii web framework" + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Behat": "src/", + "Behat\\Testwork": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "Agile", + "BDD", + "ScenarioBDD", + "Scrum", + "StoryBDD", + "User story", + "business", + "development", + "documentation", + "examples", + "symfony", + "testing" + ], + "time": "2016-12-25 13:43:52" + }, + { + "name": "behat/gherkin", + "version": "v4.4.5", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2016-10-30 11:50:56" + }, + { + "name": "behat/mink", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/Mink.git", + "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/e6930b9c74693dff7f4e58577e1b1743399f3ff9", + "reference": "e6930b9c74693dff7f4e58577e1b1743399f3ff9", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "~2.1|~3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Browser controller/emulator abstraction for PHP", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ], + "time": "2016-03-05 08:26:18" + }, + { + "name": "behat/mink-browserkit-driver", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", + "reference": "10e67fb4a295efcd62ea0bf16025a85ea19534fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/10e67fb4a295efcd62ea0bf16025a85ea19534fb", + "reference": "10e67fb4a295efcd62ea0bf16025a85ea19534fb", + "shasum": "" + }, + "require": { + "behat/mink": "^1.7.1@dev", + "php": ">=5.3.6", + "symfony/browser-kit": "~2.3|~3.0", + "symfony/dom-crawler": "~2.3|~3.0" + }, + "require-dev": { + "silex/silex": "~1.2", + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Symfony2 BrowserKit driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "Mink", + "Symfony2", + "browser", + "testing" + ], + "time": "2016-03-05 08:59:47" + }, + { + "name": "behat/mink-extension", + "version": "v2.2", + "source": { + "type": "git", + "url": "https://github.com/Behat/MinkExtension.git", + "reference": "5b4bda64ff456104564317e212c823e45cad9d59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59", + "reference": "5b4bda64ff456104564317e212c823e45cad9d59", + "shasum": "" + }, + "require": { + "behat/behat": "~3.0,>=3.0.5", + "behat/mink": "~1.5", + "php": ">=5.3.2", + "symfony/config": "~2.2|~3.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "~1.1", + "phpspec/phpspec": "~2.0" + }, + "type": "behat-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\MinkExtension": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + }, + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com" + } + ], + "description": "Mink extension for Behat", + "homepage": "http://extensions.behat.org/mink", + "keywords": [ + "browser", + "gui", + "test", + "web" + ], + "time": "2016-02-15 07:55:18" + }, + { + "name": "behat/mink-goutte-driver", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkGoutteDriver.git", + "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", + "reference": "8b9ad6d2d95bc70b840d15323365f52fcdaea6ca", + "shasum": "" + }, + "require": { + "behat/mink": "~1.6@dev", + "behat/mink-browserkit-driver": "~1.2@dev", + "fabpot/goutte": "~1.0.4|~2.0|~3.1", + "php": ">=5.3.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Goutte driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "goutte", + "headless", + "testing" + ], + "time": "2016-03-05 09:04:22" + }, + { + "name": "behat/mink-selenium2-driver", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkSelenium2Driver.git", + "reference": "473a9f3ebe0c134ee1e623ce8a9c852832020288" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/473a9f3ebe0c134ee1e623ce8a9c852832020288", + "reference": "473a9f3ebe0c134ee1e623ce8a9c852832020288", + "shasum": "" + }, + "require": { + "behat/mink": "~1.7@dev", + "instaclick/php-webdriver": "~1.1", + "php": ">=5.3.1" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Pete Otaqui", + "email": "pete@otaqui.com", + "homepage": "https://github.com/pete-otaqui" + } + ], + "description": "Selenium2 (WebDriver) driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "ajax", + "browser", + "javascript", + "selenium", + "testing", + "webdriver" + ], + "time": "2016-03-05 09:10:18" + }, + { + "name": "behat/transliterator", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "868e05be3a9f25ba6424c2dd4849567f50715003" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003", + "reference": "868e05be3a9f25ba6424c2dd4849567f50715003", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Transliterator": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "time": "2015-09-28 16:26:35" + }, + { + "name": "bossa/phpspec2-expect", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/BossaConsulting/phpspec2-expect.git", + "reference": "f3a80b7fa743b8a1078a7e320bd3e8b8b6283780" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BossaConsulting/phpspec2-expect/zipball/f3a80b7fa743b8a1078a7e320bd3e8b8b6283780", + "reference": "f3a80b7fa743b8a1078a7e320bd3e8b8b6283780", + "shasum": "" + }, + "require": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "autoload": { + "files": [ + "expect.php" + ], + "psr-0": { + "Bossa\\PhpSpec\\Expect\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marcello Duarte", + "homepage": "http://marcelloduarte.net/" + } + ], + "description": "Helper that decorates any SUS with a phpspec lazy object wrapper", + "keywords": [ + "BDD", + "SpecBDD", + "TDD", + "spec", + "specification" + ], + "time": "2014-09-12 09:25:51" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.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" + }, + "type": "library", + "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", + "time": "2017-02-14 19:40:03" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "fabpot/goutte", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Goutte.git", + "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/db5c28f4a010b4161d507d5304e28a7ebf211638", + "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "symfony/browser-kit": "~2.1|~3.0", + "symfony/css-selector": "~2.1|~3.0", + "symfony/dom-crawler": "~2.1|~3.0" + }, + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Goutte\\": "Goutte" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/FriendsOfPHP/Goutte", + "keywords": [ + "scraper" + ], + "time": "2017-01-03 13:21:43" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.2.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60", + "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.3.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "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" + ], + "time": "2016-10-08 15:01:37" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + ], + "time": "2016-12-20 10:07:11" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "0d6c7ca039329247e4f0f8f8f6506810e8248855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/0d6c7ca039329247e4f0f8f8f6506810e8248855", + "reference": "0d6c7ca039329247e4f0f8f8f6506810e8248855", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + ], + "time": "2017-02-27 10:51:17" + }, + { + "name": "instaclick/php-webdriver", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/instaclick/php-webdriver.git", + "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb", + "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "WebDriver": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Justin Bishop", + "email": "jubishop@gmail.com", + "role": "Developer" + }, + { + "name": "Anthon Pang", + "email": "apang@softwaredevelopment.ca", + "role": "Fork Maintainer" + } + ], + "description": "PHP WebDriver for Selenium 2", + "homepage": "http://instaclick.com/", + "keywords": [ + "browser", + "selenium", + "webdriver", + "webtest" + ], + "time": "2015-06-15 20:19:33" + }, + { + "name": "mayflower/php-codebrowser", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/mayflower/PHP_CodeBrowser.git", + "reference": "b44cb1867211b3eb9efe8bb61a57fe782c84831f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mayflower/PHP_CodeBrowser/zipball/b44cb1867211b3eb9efe8bb61a57fe782c84831f", + "reference": "b44cb1867211b3eb9efe8bb61a57fe782c84831f", + "shasum": "" + }, + "require": { + "monolog/monolog": "~1.7", + "phpunit/php-file-iterator": "~1.3", + "symfony/console": "~2.1|~3.0" + }, + "require-dev": { + "phploc/phploc": "*", + "phpmd/phpmd": "1.5.*", + "phpunit/phpunit": "3.7.*", + "sebastian/phpcpd": "*", + "squizlabs/php_codesniffer": "1.*" + }, + "bin": [ + "bin/phpcb" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHPCodeBrowser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Robin Gloster", + "email": "robin.gloster@mayflower.de", + "role": "developer" + } + ], + "description": "A code browser that augments the code with information from various QA tools.", + "homepage": "https://github.com/Mayflower/PHP_CodeBrowser", + "time": "2016-01-14 12:43:42" + }, + { + "name": "monolog/monolog", + "version": "1.22.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558", + "reference": "bad29cb8d18ab0315e6c477751418a82c850d558", + "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" + }, + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "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" + ], + "time": "2016-11-26 00:15:39" + }, + { + "name": "ocramius/proxy-manager", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/57e9272ec0e8deccf09421596e0e2252df440e11", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-code": ">2.2.5,<3.0" + }, + "require-dev": { + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "1.5.*" + }, + "suggest": { + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-stdlib": "To use the hydrator proxy", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "time": "2015-08-09 04:28:19" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "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.", + "time": "2016-09-30 07:12:33" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-11-25 06:54:22" + }, + { + "name": "phpspec/php-diff", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/php-diff.git", + "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a", + "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Diff": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton", + "role": "Original developer" + } + ], + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", + "time": "2013-11-01 13:02:21" + }, + { + "name": "phpspec/phpspec", + "version": "2.5.5", + "source": { + "type": "git", + "url": "https://github.com/phpspec/phpspec.git", + "reference": "db395f435eb8e820448e8690de1a8db86d5dd8af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/db395f435eb8e820448e8690de1a8db86d5dd8af", + "reference": "db395f435eb8e820448e8690de1a8db86d5dd8af", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.1", + "ext-tokenizer": "*", + "php": ">=5.3.3", + "phpspec/php-diff": "~1.0.0", + "phpspec/prophecy": "~1.4", + "sebastian/exporter": "~1.0", + "symfony/console": "~2.3|~3.0", + "symfony/event-dispatcher": "~2.1|~3.0", + "symfony/finder": "~2.1|~3.0", + "symfony/process": "^2.6|~3.0", + "symfony/yaml": "~2.1|~3.0" + }, + "require-dev": { + "behat/behat": "^3.0.11", + "ciaranmcnulty/versionbasedtestskipper": "^0.2.1", + "phpunit/phpunit": "~4.4", + "symfony/filesystem": "~2.1|~3.0" + }, + "suggest": { + "phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters" + }, + "bin": [ + "bin/phpspec" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "PhpSpec": "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", + "homepage": "http://marcelloduarte.net/" + } + ], + "description": "Specification-oriented BDD framework for PHP 5.3+", + "homepage": "http://phpspec.net/", + "keywords": [ + "BDD", + "SpecBDD", + "TDD", + "spec", + "specification", + "testing", + "tests" + ], + "time": "2016-12-04 21:03:31" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0|^2.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "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" + ], + "time": "2016-11-21 14:58:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "1.2.18", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.2.0@stable", + "phpunit/php-token-stream": ">=1.1.3,<1.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "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" + ], + "time": "2014-09-02 10:13:14" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "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" + ], + "time": "2016-10-03 07:40:28" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "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" + }, + "type": "library", + "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" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "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" + ], + "time": "2017-02-26 11:10:40" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2014-03-03 05:10:30" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.38", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.1", + "phpunit/php-timer": "~1.0", + "phpunit/phpunit-mock-objects": "~1.2", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear.php.net/pear": "1.9.4" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2014-10-17 09:04:17" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "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" + ], + "time": "2013-01-13 10:24:48" + }, + { + "name": "psr/container", + "version": "1.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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2017-02-14 16:28:37" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "psr/log", + "version": "1.0.2", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "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" + ], + "time": "2017-01-29 09:50:25" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "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" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sensiolabs/behat-page-object-extension", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/BehatPageObjectExtension.git", + "reference": "3348a58ecc907597d854d7ed4bbfe8b0eb3af709" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/BehatPageObjectExtension/zipball/3348a58ecc907597d854d7ed4bbfe8b0eb3af709", + "reference": "3348a58ecc907597d854d7ed4bbfe8b0eb3af709", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.6", + "behat/mink": "^1.6", + "behat/mink-extension": "^2.0", + "ocramius/proxy-manager": "^1.0||^2.0", + "php": ">=5.3.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "^1.0", + "bossa/phpspec2-expect": "^1.0.3||^2.0", + "fabpot/goutte": "^1.0.4||^2.0||^3.0", + "phpspec/phpspec": "^2.5||^3.0", + "symfony/filesystem": "^2.8||^3.0", + "symfony/process": "^2.8||^3.0", + "symfony/yaml": "^2.8||^3.0" + }, + "suggest": { + "bossa/phpspec2-expect": "Allows to use PHPSpec2 matchers in Behat context files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-0": { + "SensioLabs\\Behat\\PageObjectExtension\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marcello Duarte", + "email": "mduarte@inviqa.com" + }, + { + "name": "Jakub Zalas", + "email": "jakub@zalas.pl" + } + ], + "description": "Page object extension for Behat", + "homepage": "https://github.com/sensiolabs/BehatPageObjectExtension", + "keywords": [ + "BDD", + "Behat", + "page" + ], + "time": "2017-02-24 09:42:13" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "6f3e42d311b882b25b4d409d23a289f4d3b803d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6f3e42d311b882b25b4d409d23a289f4d3b803d5", + "reference": "6f3e42d311b882b25b4d409d23a289f4d3b803d5", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.1.2" + }, + "suggest": { + "phpunit/php-timer": "dev-master" + }, + "bin": [ + "scripts/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-phpcs-fixer": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/CommentParser/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2014-12-04 22:32:15" + }, + { + "name": "symfony/browser-kit", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "394a2475a3a89089353fde5714a7f402fbb83880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/394a2475a3a89089353fde5714a7f402fbb83880", + "reference": "394a2475a3a89089353fde5714a7f402fbb83880", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "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 BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2017-01-31 21:49:23" + }, + { + "name": "symfony/class-loader", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "2847d56f518ad5721bf85aa9174b3aa3fd12aa03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/2847d56f518ad5721bf85aa9174b3aa3fd12aa03", + "reference": "2847d56f518ad5721bf85aa9174b3aa3fd12aa03", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "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 ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:06:35" + }, + { + "name": "symfony/config", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "9f99453e77771e629af8a25eeb0a6c4ed1e19da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/9f99453e77771e629af8a25eeb0a6c4ed1e19da2", + "reference": "9f99453e77771e629af8a25eeb0a6c4ed1e19da2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "require-dev": { + "symfony/yaml": "~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "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", + "time": "2017-02-14 16:27:43" + }, + { + "name": "symfony/console", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0e5e6899f82230fcb1153bcaf0e106ffaa44b870" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0e5e6899f82230fcb1153bcaf0e106ffaa44b870", + "reference": "0e5e6899f82230fcb1153bcaf0e106ffaa44b870", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "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", + "time": "2017-02-16 14:07:22" + }, + { + "name": "symfony/css-selector", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f0e628f04fc055c934b3211cfabdb1c59eefbfaa", + "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:32:22" + }, + { + "name": "symfony/debug", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "9b98854cb45bc59d100b7d4cc4cf9e05f21026b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/9b98854cb45bc59d100b7d4cc4cf9e05f21026b9", + "reference": "9b98854cb45bc59d100b7d4cc4cf9e05f21026b9", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com", + "time": "2017-02-16 16:34:18" + }, + { + "name": "symfony/dependency-injection", + "version": "v3.1.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f4a04433f82eb8ca58555d1b6375293fc7c90d18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f4a04433f82eb8ca58555d1b6375293fc7c90d18", + "reference": "f4a04433f82eb8ca58555d1b6375293fc7c90d18", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~2.8.7|~3.0.7|~3.1.1|~3.2" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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 DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2017-01-28 00:04:57" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "b814b41373fc4e535aff8c765abe39545216f391" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b814b41373fc4e535aff8c765abe39545216f391", + "reference": "b814b41373fc4e535aff8c765abe39545216f391", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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 DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2017-01-21 17:14:11" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9137eb3a3328e413212826d63eeeb0217836e2b6", + "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:32:22" + }, + { + "name": "symfony/filesystem", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a0c6ef2dc78d33b58d91d3a49f49797a184d06f4", + "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "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", + "time": "2017-01-08 20:47:33" + }, + { + "name": "symfony/finder", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8c71141cae8e2957946b403cc71a67213c0380d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8c71141cae8e2957946b403cc71a67213c0380d6", + "reference": "8c71141cae8e2957946b403cc71a67213c0380d6", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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 Finder Component", + "homepage": "https://symfony.com", + "time": "2017-01-02 20:32:22" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/process", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0ab87c1e7570b3534a6e51eb4ca8e9f6d7327856" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0ab87c1e7570b3534a6e51eb4ca8e9f6d7327856", + "reference": "0ab87c1e7570b3534a6e51eb4ca8e9f6d7327856", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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 Process Component", + "homepage": "https://symfony.com", + "time": "2017-02-16 14:07:22" + }, + { + "name": "symfony/translation", + "version": "v3.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d6825c6bb2f1da13f564678f9f236fe8242c0029" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/d6825c6bb2f1da13f564678f9f236fe8242c0029", + "reference": "d6825c6bb2f1da13f564678f9f236fe8242c0029", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "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", + "time": "2017-02-16 22:46:52" + }, + { + "name": "symfony/yaml", + "version": "v2.8.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "322a8c2dfbca15ad6b1b27e182899f98ec0e0153" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/322a8c2dfbca15ad6b1b27e182899f98ec0e0153", + "reference": "322a8c2dfbca15ad6b1b27e182899f98ec0e0153", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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", + "time": "2017-01-21 16:40:50" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23 20:04:58" + }, + { + "name": "zendframework/zend-code", + "version": "2.6.3", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "95033f061b083e16cdee60530ec260d7d628b887" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/95033f061b083e16cdee60530ec260d7d628b887", + "reference": "95033f061b083e16cdee60530ec260d7d628b887", + "shasum": "" + }, + "require": { + "php": "^5.5 || 7.0.0 - 7.0.4 || ^7.0.6", + "zendframework/zend-eventmanager": "^2.6 || ^3.0" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "^4.8.21", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2016-04-20 17:26:42" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "c3bce7b7d47c54040b9ae51bc55491c72513b75d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/c3bce7b7d47c54040b9ae51bc55491c72513b75d", + "reference": "c3bce7b7d47c54040b9ae51bc55491c72513b75d", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "phpunit/phpunit": "^5.6", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + }, + "suggest": { + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev", + "dev-develop": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "event", + "eventmanager", + "events", + "zf2" + ], + "time": "2016-12-19 21:47:12" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "sensiolabs/behat-page-object-extension": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} diff --git a/features/admin/delete_image.feature b/core/features/admin/delete_image.feature old mode 100755 new mode 100644 similarity index 100% rename from features/admin/delete_image.feature rename to core/features/admin/delete_image.feature diff --git a/features/admin/extension_disable_enable.feature b/core/features/admin/extension_disable_enable.feature old mode 100755 new mode 100644 similarity index 100% rename from features/admin/extension_disable_enable.feature rename to core/features/admin/extension_disable_enable.feature diff --git a/features/admin/prompted_to_sign_up_to_cloudinary.feature b/core/features/admin/prompted_to_sign_up_to_cloudinary.feature old mode 100755 new mode 100644 similarity index 100% rename from features/admin/prompted_to_sign_up_to_cloudinary.feature rename to core/features/admin/prompted_to_sign_up_to_cloudinary.feature diff --git a/core/features/bootstrap/Domain/ConfigurationContext.php b/core/features/bootstrap/Domain/ConfigurationContext.php new file mode 100644 index 0000000..5a5b24d --- /dev/null +++ b/core/features/bootstrap/Domain/ConfigurationContext.php @@ -0,0 +1,83 @@ +configuration = Doubles::getConfiguration(); + } + + /** + * @Given I have a configuration to use multiple sub-domains + */ + public function iHaveAConfigurationToUseMultipleSubDomains() + { + Doubles::getConfigurationProphecy()->getCdnSubdomainStatus()->willReturn(true); + } + + /** + * @When I apply the configuration to the image provider + */ + public function iApplyTheConfigurationToTheImageProvider() + { + $this->imageProvider = new ConfigImageProvider($this->configuration); + } + + /** + * @Then the image provider should use multiple sub-domains + */ + public function theImageProviderShouldUseMultipleSubDomains() + { + $request1 = $this->imageProvider->retrieveTransformed(Image::fromPath('somePath'), Transformation::builder()); + $request2 = $this->imageProvider->retrieveTransformed(Image::fromPath('someOtherPath'), Transformation::builder()); + + expect($this->requestPrefixIsTheSame($request1, $request2))->toBe(false); + } + + /** + * @Given the cloudinary module is disabled + */ + public function theCloudinaryModuleIsDisabled() + { + Doubles::getConfigurationProphecy()->isEnabled()->willReturn(false); + } + + /** + * @Given the cloudinary module is enabled + */ + public function theCloudinaryModuleIsEnabled() + { + Doubles::getConfigurationProphecy()->isEnabled()->willReturn(true); + } + + private function requestPrefixIsTheSame($request1, $request2) + { + return substr($request1, 0, 4) === substr($request2, 0, 4); + } +} \ No newline at end of file diff --git a/core/features/bootstrap/Domain/DeleteImageDomainContext.php b/core/features/bootstrap/Domain/DeleteImageDomainContext.php new file mode 100644 index 0000000..f79cda3 --- /dev/null +++ b/core/features/bootstrap/Domain/DeleteImageDomainContext.php @@ -0,0 +1,57 @@ +imageProvider = new FakeImageProvider($environmentVariable); + + $this->imageProvider->upload($anImage); + } + + /** + * @When I delete the :anImage image + */ + public function iDeleteTheImage($anImage) + { + $this->imageProvider->delete($anImage); + } + + /** + * @Then the image :anImage should no longer be available in the image provider + */ + public function theImageShouldNoLongerBeAvailableInTheImageProvider($anImage) + { + expect($this->imageProvider->retrieveTransformed($anImage, Transformation::builder()))->toBe(''); + } + +} diff --git a/core/features/bootstrap/Domain/DomainContext.php b/core/features/bootstrap/Domain/DomainContext.php new file mode 100644 index 0000000..5992a8f --- /dev/null +++ b/core/features/bootstrap/Domain/DomainContext.php @@ -0,0 +1,215 @@ +provider = new FakeImageProvider($environmentVariable); + + $cloud = Cloud::fromName('session-digital'); + $key = Key::fromString('ABC123'); + $secret = Secret::fromString('DEF456'); + $this->provider->setMockCloud($cloud); + $this->provider->setMockCredentials($key, $secret); + } + + /** + * @Transform :anImage + */ + public function transformStringToAnImage($string) + { + return Image::fromPath($string); + } + + /** + * @Given I have an image :anImage + */ + public function iHaveAnImage(Image $anImage) + { + $this->image = $anImage; + } + + /** + * @When I upload the image :anImage + */ + public function iUploadTheImage(Image $anImage) + { + try { + $this->provider->upload($anImage); + } catch (\Exception $e) { + $this->impageAlreadyUplaoded = true; + } + } + + /** + * @Given the image :anImage does not exist on the provider + */ + public function theImageDoesNotExistOnTheProvider(Image $anImage) + { + expect($this->provider->getImageUrlByName((string)$anImage))->toBe(''); + } + + /** + * @Given the image :anImage has already been uploaded + */ + public function theImageHasAlreadyBeenUploaded(Image $anImage) + { + try { + $this->provider->upload($anImage); + } catch (\Exception $e) { + $this->impageAlreadyUplaoded = true; + } + } + + /** + * @Then the image :anImage will be provided remotely + */ + public function theImageWillBeProvidedRemotely(Image $anImage) + { + $this->setupStubs($anImage); + + $imageFactory = new ImageFactory(Doubles::getConfiguration(), Doubles::getSynchronizationChecker()); + + $image = $imageFactory->build((string)$anImage, [$this, 'getLocalUrl']); + + $urlGenerator = new UrlGenerator(Doubles::getConfiguration(), $this->provider); + + expect($urlGenerator->generateFor($image))->toBe('uploaded image URL'); + } + + /** + * @Then I should see an error image already exists + */ + public function iShouldSeeAnErrorImageAlreadyExists() + { + expect($this->impageAlreadyUplaoded)->toBe(true); + } + + /** + * @Then the image should be available through the image provider + */ + public function theImageShouldBeAvailableThroughTheImageProvider() + { + expect($this->provider->getImageUrlByName($this->getImageName()))->notToBe(''); + } + + private function getImageName() + { + $imagePath = explode('/', $this->image); + return $imagePath[count($imagePath) - 1]; + } + + /** + * @Given I have used a valid environment variable in the configuration + */ + public function iHaveUsedAValidEnvironmentVariableInTheConfiguration() + { + $environmentVariable = CloudinaryEnvironmentVariable::fromString('CLOUDINARY_URL=cloudinary://ABC123:DEF456@session-digital'); + $this->provider = new FakeImageProvider($environmentVariable); + } + + /** + * @Given I have used an invalid environment variable in the configuration + */ + public function iHaveUsedAnInvalidEnvironmentVariableInTheConfiguration() + { + $environmentVariable = CloudinaryEnvironmentVariable::fromString('CLOUDINARY_URL=cloudinary://UVW789:XYZ123@session-digital'); + $this->provider = new FakeImageProvider($environmentVariable); + } + + /** + * @When I ask the provider to validate my credentials + */ + public function iAskTheProviderToValidateMyCredentials() + { + $cloud = Cloud::fromName('session-digital'); + $key = Key::fromString('ABC123'); + $secret = Secret::fromString('DEF456'); + $this->provider->setMockCloud($cloud); + $this->provider->setMockCredentials($key, $secret); + + $this->areCredentialsValid = $this->provider->validateCredentials(); + } + + /** + * @Then I should be informed my credentials are valid + */ + public function iShouldBeInformedMyCredentialsAreValid() + { + expect($this->areCredentialsValid)->toBe(true); + } + + /** + * @Then I should be informed that my credentials are not valid + */ + public function iShouldBeInformedThatMyCredentialsAreNotValid() + { + expect($this->areCredentialsValid)->toBe(false); + } + + /** + * @Given I am logged in as an administrator + */ + public function iAmLoggedInAsAnAdministrator() + { + // not required for domain suite + } + + /** + * @Then the image :anImage will be provided locally + */ + public function theImageWillBeProvidedLocally(Image $anImage) + { + $this->setupStubs($anImage); + + $imageFactory = new ImageFactory(Doubles::getConfiguration(), Doubles::getSynchronizationChecker()); + + $image = $imageFactory->build((string)$anImage, [$this, 'getLocalUrl']); + + $urlGenerator = new UrlGenerator(Doubles::getConfiguration(), $this->provider); + + expect($urlGenerator->generateFor($image))->toBe('local image path'); + } + + public function getLocalUrl() + { + return 'local image path'; + } + + /** + * @param Image $anImage + */ + private function setupStubs(Image $anImage) + { + Doubles::getConfigurationProphecy()->getMigratedPath((string)$anImage)->willReturn((string)$anImage); + Doubles::getSynchronizationCheckerProphecy()->isSynchronized((string)$anImage)->willReturn(true); + } +} diff --git a/core/features/bootstrap/Domain/Doubles.php b/core/features/bootstrap/Domain/Doubles.php new file mode 100644 index 0000000..ae312c3 --- /dev/null +++ b/core/features/bootstrap/Domain/Doubles.php @@ -0,0 +1,63 @@ +prophesize(ConfigurationInterface::class); + self::$synchronizationCheckerProphecy = (new Prophet())->prophesize(SynchronizationChecker::class); + self::$configurationProphecy->getFormatsToPreserve()->willReturn([]); + self::$setupDone = true; + } + } + + public static function getConfiguration() + { + self::setup(); + return self::$configurationProphecy->reveal(); + } + + public static function getSynchronizationChecker() + { + self::setup(); + return self::$synchronizationCheckerProphecy->reveal(); + } + + public static function getConfigurationProphecy() + { + self::setup(); + return self::$configurationProphecy; + } + + public static function getSynchronizationCheckerProphecy() + { + self::setup(); + return self::$synchronizationCheckerProphecy; + } + + +} \ No newline at end of file diff --git a/core/features/bootstrap/Domain/TransformationContext.php b/core/features/bootstrap/Domain/TransformationContext.php new file mode 100644 index 0000000..ee33742 --- /dev/null +++ b/core/features/bootstrap/Domain/TransformationContext.php @@ -0,0 +1,225 @@ +configuration = Doubles::getConfiguration(); + + $defaultTransformation = (new Transformation()) + ->withQuality(Quality::fromString('80')) + ->withDpr(Dpr::fromString('1.0')); + + Doubles::getConfigurationProphecy()->getDefaultTransformation()->willReturn($defaultTransformation); + + $this->imageProvider = new TransformingImageProvider($this->configuration); + } + + /** + * @Transform :aDpr + */ + public function transformStringToDpr($string) + { + return Dpr::fromString($string); + } + + /** + * @Transform :aQuality + */ + public function transformStringToQuality($string) + { + return Quality::fromString($string); + } + + /** + * @Transform :aDimension + */ + public function transformStringToDimensions($string) + { + $dimensions = explode('x', $string); + + return Dimensions::fromWidthAndHeight($dimensions[0], $dimensions[1]); + } + + /** + * @Given there's an image :anImage in the image provider + */ + public function thereIsAnImageInTheImageProvider(Image $anImage) + { + $this->image = $anImage; + $this->imageProvider->upload($this->image); + } + + /** + * @When I request the image from the image provider + */ + public function iRequestTheImageFromTheImageProvider() + { + $this->imageUrl = $this->imageProvider->retrieve($this->image); + } + + /** + * @Then I should get an optimised image from the image provider + */ + public function iShouldGetAnOptimisedImageFromTheImageProvider() + { + expect($this->urlIsOptimised())->toBe(true); + } + + /** + * @Given image optimisation is disabled + */ + public function imageOptimisationIsDisabled() + { + $this->configuration->getDefaultTransformation()->withOptimisationDisabled(); + } + + /** + * @Then I should get the original image from the image provider + */ + public function iShouldGetTheOriginalImageFromTheImageProvider() + { + expect($this->isOriginalImage())->toBe(true); + } + + /** + * @Then I should get an image with :aQuality percent quality from the image provider + */ + public function iShouldGetAnImageWithPercentQualityFromTheImageProvider(Quality $aQuality) + { + expect($this->isPercentageQuality((string)$aQuality))->toBe(true); + } + + /** + * @Given I set image quality to :aQuality percent + */ + public function iTransformTheImageToHavePercentQuality(Quality $aQuality) + { + $transformation = $this->configuration->getDefaultTransformation(); + + Doubles::getConfigurationProphecy() + ->getDefaultTransformation() + ->willReturn($transformation->withQuality($aQuality)); + } + + /** + * @When I ask the image provider for :imageName transformed to :aDimension + */ + public function iRequestTheImageProvideForTransformedTo($imageName, Dimensions $aDimension) + { + $this->imageUrl = $this->imageProvider->retrieveTransformed( + Image::fromPath($imageName), + Transformation::builder()->withDimensions($aDimension) + ); + } + + /** + * @Then I should receive that image with the dimensions :aDimension + */ + public function iShouldReceiveThatImageWithTheDimensions(Dimensions $aDimension) + { + expect($this->hasDimensions($aDimension))->toBe(true); + } + + /** + * @Then I should get the image :image with the default DPR + */ + public function iShouldGetAnImageWithTheDefaultDpr($image) + { + expect(basename($this->imageUrl))->toBe($image); + expect($this->hasDefaultDpr())->toBe(true); + } + + /** + * @Given my DPR is set to :aDpr in the configuration + */ + public function myDprIsSetToInTheConfiguration(Dpr $aDpr) + { + $transformation = $this->configuration->getDefaultTransformation(); + + Doubles::getConfigurationProphecy() + ->getDefaultTransformation() + ->willReturn($transformation->withDpr($aDpr)); + + } + + /** + * @Then I should get an image with DPR :aDpr + */ + public function iShouldGetAnImageWithDpr(Dpr $aDpr) + { + expect($this->hasDpr($aDpr))->toBe(true); + } + + private function urlIsOptimised() + { + return strpos($this->imageUrl, 'fetch_format=auto') !== false; + } + + private function isPercentageQuality($percentage) + { + return strpos($this->imageUrl, "quality=$percentage") !== false; + } + + private function hasDimensions(Dimensions $dimension) + { + $hasWidth = strpos($this->imageUrl, "width={$dimension->getWidth()}") !== false; + $hasHeight = strpos($this->imageUrl, "height={$dimension->getHeight()}") !== false; + return $hasWidth && $hasHeight; + } + + private function hasDefaultDpr() + { + return $this->hasDpr('1.0'); + } + + private function hasDpr($dpr) + { + return strpos($this->imageUrl, "dpr=$dpr") !== false; + } + + /** + * @return bool + */ + protected function isOriginalImage() + { + return strpos($this->imageUrl, $this->image->__toString()) !== false && + strpos($this->imageUrl, '&quality=80&') !== false && + strpos($this->imageUrl, '&dpr=1.0/') !== false; + + } +} \ No newline at end of file diff --git a/features/bootstrap/Fixtures/Admin.yaml b/core/features/bootstrap/Fixtures/Admin.yaml old mode 100755 new mode 100644 similarity index 100% rename from features/bootstrap/Fixtures/Admin.yaml rename to core/features/bootstrap/Fixtures/Admin.yaml diff --git a/core/features/bootstrap/ImageProviders/ConfigImageProvider.php b/core/features/bootstrap/ImageProviders/ConfigImageProvider.php new file mode 100644 index 0000000..83e4a02 --- /dev/null +++ b/core/features/bootstrap/ImageProviders/ConfigImageProvider.php @@ -0,0 +1,51 @@ +configuration = $configuration; + } + + public function upload(Image $image) + { + } + + public function retrieveTransformed(Image $image, Transformation $transformation) + { + $prefix = $this->subdomains[$this->prefixCount % 2]; + + if($this->configuration->getCdnSubdomainStatus() === true) + { + $this->prefixCount += 1; + } + + return $prefix . "/" . $image; + } + + public function retrieve(Image $image) + { + + } + + public function delete(Image $image) + { + } + + public function validateCredentials() + { + } +} \ No newline at end of file diff --git a/core/features/bootstrap/ImageProviders/ConfigurationProvider.php b/core/features/bootstrap/ImageProviders/ConfigurationProvider.php new file mode 100644 index 0000000..237824e --- /dev/null +++ b/core/features/bootstrap/ImageProviders/ConfigurationProvider.php @@ -0,0 +1,119 @@ +defaultTransformation = Transformation::builder(); + $this->isEnabledCdnSubdomain = false; + } + /** + * @return Cloud + */ + public function getCloud() + { + return Cloud::fromName('aCloudName'); + } + + /** + * @return Credentials + */ + public function getCredentials() + { + return Credentials::fromKeyAndSecret( + Key::fromString('aKey'), + Secret::fromString('aSecret') + ); + } + + /** + * @return Transformation + */ + public function getDefaultTransformation() + { + return $this->defaultTransformation; + } + + /** + * @param Transformation $transformation + */ + public function setDefaultTransformation(Transformation $transformation) + { + $this->defaultTransformation = $transformation; + } + + /** + * @return boolean + */ + public function getCdnSubdomainStatus() + { + return $this->isEnabledCdnSubdomain; + } + + /** + * @return string + */ + public function getUserPlatform() + { + + } + + /** + * @return UploadConfig + */ + public function getUploadConfig() + { + + } + + /** + * @return boolean + */ + public function isEnabled() + { + + } + + public function enable() + { + + } + + public function disable() + { + + } + + /** + * @return array + */ + public function getFormatsToPreserve() + { + + } + + public function enableCdnSubdomain() + { + $this->isEnabledCdnSubdomain = true; + } +} \ No newline at end of file diff --git a/core/features/bootstrap/ImageProviders/FakeImageProvider.php b/core/features/bootstrap/ImageProviders/FakeImageProvider.php new file mode 100644 index 0000000..b7355cf --- /dev/null +++ b/core/features/bootstrap/ImageProviders/FakeImageProvider.php @@ -0,0 +1,88 @@ +credentials = $environmentVariable->getCredentials(); + $this->cloud = $environmentVariable->getCloud(); + } + + public function setMockCredentials(Key $aKey, Secret $aSecret) + { + $this->key = $aKey; + $this->secret = $aSecret; + } + + public function setMockCloud(Cloud $mockCloud) + { + $this->mockCloud = $mockCloud; + } + + public function upload(Image $image) + { + if (array_key_exists((string)$image, $this->uploadedImageUrl)) { + throw new \Exception('Image already exist at the provider'); + } + $this->uploadedImageUrl[(string)$image] = 'uploaded image URL'; + } + + public function getImageUrlByName($image, $options = array()) + { + $imageName = (string)$image; + if($this->areCredentialsCorrect() && $this->isCloudCorrect()) { + return array_key_exists($imageName, $this->uploadedImageUrl) ? $this->uploadedImageUrl[$imageName] : ''; + } + return ''; + } + + public function validateCredentials() + { + return $this->areCredentialsCorrect(); + } + + private function areCredentialsCorrect() + { + return (string)$this->credentials->getKey() === (string)$this->key && (string)$this->credentials->getSecret() === (string)$this->secret; + } + + private function isCloudCorrect() + { + return (string)$this->mockCloud == (string)$this->cloud; + } + + public function retrieveTransformed(Image $image, \CloudinaryExtension\Image\Transformation $transformation) + { + $imageName = (string)$image; + if($this->areCredentialsCorrect() && $this->isCloudCorrect()) { + return array_key_exists($imageName, $this->uploadedImageUrl) ? $this->uploadedImageUrl[$imageName] : ''; + } + return ''; + } + + public function retrieve(Image $image) + { + + } + + public function delete(Image $image) + { + unset($this->uploadedImageUrl[(string)$image]); + } +} \ No newline at end of file diff --git a/core/features/bootstrap/ImageProviders/TransformingImageProvider.php b/core/features/bootstrap/ImageProviders/TransformingImageProvider.php new file mode 100644 index 0000000..a0121ee --- /dev/null +++ b/core/features/bootstrap/ImageProviders/TransformingImageProvider.php @@ -0,0 +1,46 @@ +configuration = $configuration; + } + + public function upload(Image $image) + { + $this->images[(string)$image] = $image; + } + + public function retrieveTransformed(Image $image, Transformation $transformation) + { + return http_build_query($transformation->build()) .'/'. $this->images[(string)$image]; + } + + public function retrieve(Image $image) + { + return $this->retrieveTransformed($image, $this->configuration->getDefaultTransformation()); + } + + public function delete(Image $image) + { + } + + public function validateCredentials() + { + } + +} \ No newline at end of file diff --git a/features/bootstrap/Page/AdminLogin.php b/core/features/bootstrap/Page/AdminLogin.php old mode 100755 new mode 100644 similarity index 100% rename from features/bootstrap/Page/AdminLogin.php rename to core/features/bootstrap/Page/AdminLogin.php diff --git a/features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php b/core/features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php old mode 100755 new mode 100644 similarity index 100% rename from features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php rename to core/features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php diff --git a/features/bootstrap/Page/CloudinaryManagement.php b/core/features/bootstrap/Page/CloudinaryManagement.php old mode 100755 new mode 100644 similarity index 100% rename from features/bootstrap/Page/CloudinaryManagement.php rename to core/features/bootstrap/Page/CloudinaryManagement.php diff --git a/features/bootstrap/Ui/AdminCredentialsContext.php b/core/features/bootstrap/Ui/AdminCredentialsContext.php old mode 100755 new mode 100644 similarity index 100% rename from features/bootstrap/Ui/AdminCredentialsContext.php rename to core/features/bootstrap/Ui/AdminCredentialsContext.php diff --git a/features/bootstrap/Ui/ModuleEnableContext.php b/core/features/bootstrap/Ui/ModuleEnableContext.php old mode 100755 new mode 100644 similarity index 100% rename from features/bootstrap/Ui/ModuleEnableContext.php rename to core/features/bootstrap/Ui/ModuleEnableContext.php diff --git a/features/configuration.feature b/core/features/configuration.feature old mode 100755 new mode 100644 similarity index 100% rename from features/configuration.feature rename to core/features/configuration.feature diff --git a/features/image_provider_transform.feature b/core/features/image_provider_transform.feature old mode 100755 new mode 100644 similarity index 100% rename from features/image_provider_transform.feature rename to core/features/image_provider_transform.feature diff --git a/core/features/image_provider_upload.feature b/core/features/image_provider_upload.feature new file mode 100644 index 0000000..11dd860 --- /dev/null +++ b/core/features/image_provider_upload.feature @@ -0,0 +1,27 @@ +@javascript @critical +Feature: Uploading images to an image provider + In order to optimise images for specific web clients + As a Store Admin + I want to be able to upload images to the image provider + + Background: + Given I am logged in as an administrator + And the cloudinary module is disabled + And the image "pink_dress.gif" does not exist on the provider + + Scenario: Image is provided locally when module is disabled + Given the cloudinary module is disabled + When I upload the image "pink_dress.gif" + Then the image "pink_dress.gif" will be provided locally + + Scenario: Image is provided remotely when module is enabled + Given the cloudinary module is enabled + And the image "pink_dress.gif" does not exist on the provider + When I upload the image "pink_dress.gif" + Then the image "pink_dress.gif" will be provided remotely + + Scenario: Image with same ID already exists in the provider + Given the cloudinary module is enabled + But the image "pink_dress.gif" has already been uploaded + When I upload the image "pink_dress.gif" + Then I should see an error image already exists diff --git a/features/migration/admin_migrates_images.feature b/core/features/migration/admin_migrates_images.feature old mode 100755 new mode 100644 similarity index 100% rename from features/migration/admin_migrates_images.feature rename to core/features/migration/admin_migrates_images.feature diff --git a/features/migration/cloudinary_enable_disable.feature b/core/features/migration/cloudinary_enable_disable.feature old mode 100755 new mode 100644 similarity index 100% rename from features/migration/cloudinary_enable_disable.feature rename to core/features/migration/cloudinary_enable_disable.feature diff --git a/features/validate_credentials.feature b/core/features/validate_credentials.feature old mode 100755 new mode 100644 similarity index 100% rename from features/validate_credentials.feature rename to core/features/validate_credentials.feature diff --git a/core/lib/CloudinaryExtension/Cloud.php b/core/lib/CloudinaryExtension/Cloud.php new file mode 100644 index 0000000..559a02c --- /dev/null +++ b/core/lib/CloudinaryExtension/Cloud.php @@ -0,0 +1,25 @@ +cloudName = (string)$cloudName; + } + + public static function fromName($aCloudName) + { + return new Cloud($aCloudName); + } + + public function __toString() + { + return $this->cloudName; + } +} diff --git a/core/lib/CloudinaryExtension/CloudinaryImageManager.php b/core/lib/CloudinaryExtension/CloudinaryImageManager.php new file mode 100644 index 0000000..62813bc --- /dev/null +++ b/core/lib/CloudinaryExtension/CloudinaryImageManager.php @@ -0,0 +1,52 @@ +cloudinaryImageProvider = $cloudinaryImageProvider; + $this->synchronisationRepository = $synchronisationRepository; + } + + /** + * @param Image $image + */ + public function uploadAndSynchronise(Image $image) + { + $this->cloudinaryImageProvider->upload($image); + $this->synchronisationRepository->saveAsSynchronized($image->getRelativePath()); + + } + + /** + * @param Image $image + */ + public function removeAndUnSynchronise(Image $image) + { + $this->cloudinaryImageProvider->delete($image); + $this->synchronisationRepository->removeSynchronised($image->getRelativePath()); + } +} \ No newline at end of file diff --git a/core/lib/CloudinaryExtension/CloudinaryImageProvider.php b/core/lib/CloudinaryExtension/CloudinaryImageProvider.php new file mode 100644 index 0000000..dd1852f --- /dev/null +++ b/core/lib/CloudinaryExtension/CloudinaryImageProvider.php @@ -0,0 +1,96 @@ +configuration = $configuration; + $this->uploadResponseValidator = $uploadResponseValidator; + $this->configurationBuilder = $configurationBuilder; + $this->credentialValidator = $credentialValidator; + $this->authorise(); + } + + public static function fromConfiguration(ConfigurationInterface $configuration){ + return new CloudinaryImageProvider( + $configuration, + new ConfigurationBuilder($configuration), + new UploadResponseValidator(), + new CredentialValidator() + ); + } + + public function upload(Image $image) + { + try { + $uploadResult = Uploader::upload( + (string)$image, + $this->configuration->getUploadConfig()->toArray() + [ "folder" => $image->getRelativeFolder()] + ); + + return $this->uploadResponseValidator->validateResponse($image, $uploadResult); + + } catch (\Exception $e) { + MigrationError::throwWith($image, MigrationError::CODE_API_ERROR, $e->getMessage()); + } + } + + public function retrieveTransformed(Image $image, Transformation $transformation) + { + return Image::fromPath( + \cloudinary_url($image->getId(), $transformation->build() + ["secure" => true]), + $image->getRelativePath() + ); + } + + public function retrieve(Image $image) + { + return $this->retrieveTransformed($image, $this->configuration->getDefaultTransformation()); + } + + public function delete(Image $image) + { + Uploader::destroy($image->getId()); + } + + public function validateCredentials() + { + return $this->credentialValidator->validate($this->configuration->getCredentials()); + } + + private function authorise() + { + Cloudinary::config($this->configurationBuilder->build()); + Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform(); + } +} \ No newline at end of file diff --git a/core/lib/CloudinaryExtension/ConfigurationBuilder.php b/core/lib/CloudinaryExtension/ConfigurationBuilder.php new file mode 100644 index 0000000..7bb9c82 --- /dev/null +++ b/core/lib/CloudinaryExtension/ConfigurationBuilder.php @@ -0,0 +1,30 @@ +configuration = $configuration; + } + + public function build() + { + $config = [ + "cloud_name" => (string)$this->configuration->getCloud(), + "api_key" => (string)$this->configuration->getCredentials()->getKey(), + "api_secret" => (string)$this->configuration->getCredentials()->getSecret() + ]; + + if ($this->configuration->getCdnSubdomainStatus()) { + $config['cdn_subdomain'] = true; + } + return $config; + } +} diff --git a/core/lib/CloudinaryExtension/ConfigurationInterface.php b/core/lib/CloudinaryExtension/ConfigurationInterface.php new file mode 100644 index 0000000..0bb3bfb --- /dev/null +++ b/core/lib/CloudinaryExtension/ConfigurationInterface.php @@ -0,0 +1,67 @@ +validate(); + + } +} \ No newline at end of file diff --git a/core/lib/CloudinaryExtension/Credentials.php b/core/lib/CloudinaryExtension/Credentials.php new file mode 100644 index 0000000..56b5cd5 --- /dev/null +++ b/core/lib/CloudinaryExtension/Credentials.php @@ -0,0 +1,35 @@ +key = $key; + $this->secret = $secret; + } + + public static function fromKeyAndSecret(Key $key,Secret $secret) + { + return new Credentials($key, $secret); + } + + public function getKey() + { + return $this->key; + } + + public function getSecret() + { + return $this->secret; + } +} diff --git a/core/lib/CloudinaryExtension/Exception/InvalidCredentials.php b/core/lib/CloudinaryExtension/Exception/InvalidCredentials.php new file mode 100644 index 0000000..5dd83b8 --- /dev/null +++ b/core/lib/CloudinaryExtension/Exception/InvalidCredentials.php @@ -0,0 +1,10 @@ + 'File already exists (cloudinary is case insensitive!!).', + self::CODE_API_ERROR => 'Internal API error' + ]; + + private $image; + + /** + * @return Image + */ + public function getImage() + { + return $this->image; + } + + /** + * @param Image $image + * @param $code + * @param $message overrides the default message attached to the code + * @return MigrationError + */ + private static function build(Image $image, $code, $message = '') + { + $result = new MigrationError($message ?: self::$messages[$code], $code); + $result->image = $image; + return $result; + } + + public static function throwWith(Image $image, $code, $message = '') + { + throw MigrationError::build($image, $code, $message); + } +} diff --git a/core/lib/CloudinaryExtension/FolderTranslator.php b/core/lib/CloudinaryExtension/FolderTranslator.php new file mode 100644 index 0000000..2949dc6 --- /dev/null +++ b/core/lib/CloudinaryExtension/FolderTranslator.php @@ -0,0 +1,16 @@ +imagePath = $imagePath; + $this->relativePath = $relativePath; + $this->pathInfo = pathinfo($this->imagePath); + } + + public static function fromPath($imagePath, $relativePath = '') + { + return new Image($imagePath, $relativePath); + } + + public function __toString() + { + return $this->imagePath; + } + + public function getRelativePath() + { + return $this->relativePath; + } + + public function getRelativeFolder() + { + $result = dirname($this->getRelativePath()); + return $result == '.' ? '' : $result; + } + + public function getId() + { + if ($this->relativePath) { + return $this->getRelativeFolder() . DIRECTORY_SEPARATOR . $this->pathInfo['filename']; + } else { + return $this->pathInfo['filename']; + } + } + + public function getExtension() + { + return $this->pathInfo['extension']; + } +} diff --git a/core/lib/CloudinaryExtension/Image/ImageFactory.php b/core/lib/CloudinaryExtension/Image/ImageFactory.php new file mode 100644 index 0000000..8866785 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/ImageFactory.php @@ -0,0 +1,46 @@ +configuration = $configuration; + $this->synchronizationChecker = $synchronizationChecker; + } + + /** + * @param $imagePath + * @return Image + */ + public function build($imagePath, callable $localPathGenerator) + { + $migratedPath = $this->configuration->getMigratedPath($imagePath); + + if ($this->configuration->isEnabled() && $this->synchronizationChecker->isSynchronized($migratedPath)) { + return Image::fromPath($imagePath, $migratedPath); + } else { + return new LocalImage($localPathGenerator); + } + } +} diff --git a/core/lib/CloudinaryExtension/Image/LocalImage.php b/core/lib/CloudinaryExtension/Image/LocalImage.php new file mode 100644 index 0000000..556a555 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/LocalImage.php @@ -0,0 +1,25 @@ +localPathGenerator = $localPathGenerator; + } + + public function __toString() + { + return call_user_func($this->localPathGenerator); + } +} diff --git a/core/lib/CloudinaryExtension/Image/Synchronizable.php b/core/lib/CloudinaryExtension/Image/Synchronizable.php new file mode 100644 index 0000000..c9d22d4 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Synchronizable.php @@ -0,0 +1,21 @@ +fetchFormat = FetchFormat::fromString(Format::FETCH_FORMAT_AUTO); + $this->crop = 'pad'; + $this->format = Format::fromExtension('jpg'); + $this->validFormats = array('gif', 'jpg', 'png', 'svg', 'webp'); + $this->flags = []; + } + + public function withGravity(Gravity $gravity) + { + $this->gravity = $gravity; + $this->crop = ((string)$gravity) ? 'crop' : 'pad'; + return $this; + } + + public function withDimensions(Dimensions $dimensions) + { + $this->dimensions = $dimensions; + return $this; + } + + public function withFetchFormat(FetchFormat $fetchFormat) + { + $this->fetchFormat = $fetchFormat; + return $this; + } + + public function withFormat(Format $format) + { + if (in_array((string)$format, $this->validFormats)) { + $this->format = $format; + } + + return $this; + } + + public function withoutFormat() + { + $this->format = null; + return $this; + } + + public function withQuality(Quality $quality) + { + $this->quality = $quality; + return $this; + } + + public function withDpr(Dpr $dpr) + { + $this->dpr = $dpr; + return $this; + } + + public function withOptimisationDisabled() + { + return $this->withFetchFormat(FetchFormat::fromString('')); + } + + public function addFlags(array $flags = []) + { + $this->flags += $flags; + return $this; + } + + public static function builder() + { + return new Transformation(); + } + + public function build() + { + return array( + 'fetch_format' => (string)$this->fetchFormat, + 'quality' => (string)$this->quality, + 'crop' => (string)$this->crop, + 'gravity' => (string)$this->gravity ?: null, + 'width' => $this->dimensions ? $this->dimensions->getWidth() : null, + 'height' => $this->dimensions ? $this->dimensions->getHeight() : null, + 'format' => (string)$this->format, + 'dpr' => (string)$this->dpr, + 'flags' => $this->flags + ); + } +} + diff --git a/core/lib/CloudinaryExtension/Image/Transformation/Dimensions.php b/core/lib/CloudinaryExtension/Image/Transformation/Dimensions.php new file mode 100644 index 0000000..ec0fa8b --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Transformation/Dimensions.php @@ -0,0 +1,36 @@ +width = is_null($width) ? null : (int) round($width); + $this->height = is_null($height) ? null : (int) round($height); + } + + public function getWidth() + { + return $this->width; + } + + public function getHeight() + { + return $this->height; + } + + public static function fromWidthAndHeight($width, $height) + { + return new Dimensions($width, $height); + } + + public static function null() + { + return new Dimensions(null, null); + } +} diff --git a/core/lib/CloudinaryExtension/Image/Transformation/Dpr.php b/core/lib/CloudinaryExtension/Image/Transformation/Dpr.php new file mode 100644 index 0000000..8c15aa5 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Transformation/Dpr.php @@ -0,0 +1,23 @@ +value = $value; + } + + public static function fromString($value) + { + return new Dpr($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/core/lib/CloudinaryExtension/Image/Transformation/FetchFormat.php b/core/lib/CloudinaryExtension/Image/Transformation/FetchFormat.php new file mode 100644 index 0000000..8b34308 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Transformation/FetchFormat.php @@ -0,0 +1,25 @@ +value = $value; + } + + public static function fromString($value) + { + return new FetchFormat($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/core/lib/CloudinaryExtension/Image/Transformation/Format.php b/core/lib/CloudinaryExtension/Image/Transformation/Format.php new file mode 100644 index 0000000..94da009 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Transformation/Format.php @@ -0,0 +1,25 @@ +value = $value; + } + + public static function fromExtension($value) + { + return new Format($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/core/lib/CloudinaryExtension/Image/Transformation/Gravity.php b/core/lib/CloudinaryExtension/Image/Transformation/Gravity.php new file mode 100644 index 0000000..9eb0d1a --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Transformation/Gravity.php @@ -0,0 +1,30 @@ +value = $value; + } + + public function __toString() + { + return $this->value; + } + + public static function fromString($value) + { + return new Gravity($value); + } + + public static function null() + { + return new Gravity(null); + } +} + + diff --git a/core/lib/CloudinaryExtension/Image/Transformation/Quality.php b/core/lib/CloudinaryExtension/Image/Transformation/Quality.php new file mode 100644 index 0000000..6b7f4d3 --- /dev/null +++ b/core/lib/CloudinaryExtension/Image/Transformation/Quality.php @@ -0,0 +1,23 @@ +value = $value; + } + + public static function fromString($value) + { + return new Quality($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/core/lib/CloudinaryExtension/ImageInterface.php b/core/lib/CloudinaryExtension/ImageInterface.php new file mode 100644 index 0000000..ffc3293 --- /dev/null +++ b/core/lib/CloudinaryExtension/ImageInterface.php @@ -0,0 +1,11 @@ +imageProvider = $imageProvider; + $this->migrationTask = $migrationTask; + $this->baseMediaPath = $baseMediaPath; + $this->logger = $logger; + } + + public function uploadImages(array $images) + { + $this->countMigrated = 0; + foreach ($images as $image) { + + if ($this->migrationTask->hasBeenStopped()) { + break; + } + $this->uploadImage($image); + } + $this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated, $this->countFailed)); + } + + private function getAbsolutePath(Synchronizable $image) + { + return sprintf('%s%s', $this->baseMediaPath, $image->getFilename()); + } + + private function uploadImage(Synchronizable $image) + { + $absolutePath = $this->getAbsolutePath($image); + $relativePath = $image->getRelativePath(); + $apiImage = Image::fromPath($absolutePath, $relativePath); + + try { + $this->imageProvider->upload($apiImage); + $image->tagAsSynchronized(); + $this->countMigrated++; + $this->logger->notice(sprintf(self::MESSAGE_UPLOADED, $absolutePath . ' - ' . $relativePath)); + } catch (\Exception $e) { + $this->errors[] = $e; + $this->countFailed++; + $this->logger->error(sprintf(self::MESSAGE_UPLOAD_ERROR, $e->getMessage(), $absolutePath . ' - ' . $relativePath)); + } + } + + /** + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + public function getMigrationErrors() + { + return array_filter($this->errors, function ($val) { + return $val instanceof MigrationError; + }); + } + +} diff --git a/core/lib/CloudinaryExtension/Migration/Logger.php b/core/lib/CloudinaryExtension/Migration/Logger.php new file mode 100644 index 0000000..74bba5a --- /dev/null +++ b/core/lib/CloudinaryExtension/Migration/Logger.php @@ -0,0 +1,14 @@ +migrationTask = $migrationTask; + $this->synchronizedMediaRepository = $synchronizedMediaRepository; + $this->logger = $logger; + $this->batchUploader = $batchUploader; + } + + public function process() + { + if ($this->migrationTask->hasBeenStopped()) { + return; + } + + $images = $this->synchronizedMediaRepository->findUnsynchronisedImages(); + + if (!$images) { + $this->logger->notice(self::MESSAGE_COMPLETE); + $this->migrationTask->stop(); + } else { + $this->logger->notice(self::MESSAGE_PROCESSING); + $this->batchUploader->uploadImages($images); + } + } +} diff --git a/core/lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php b/core/lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php new file mode 100644 index 0000000..c01dc34 --- /dev/null +++ b/core/lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php @@ -0,0 +1,8 @@ +apiSignature = Cloudinary::api_sign_request($params, (string) $secret); + } + + public static function fromSecretAndParams(Secret $secret, array $params = array()) + { + return new ApiSignature($secret, $params); + } + + public function __toString() + { + return $this->apiSignature; + } +} diff --git a/core/lib/CloudinaryExtension/Security/CloudinaryEnvironmentVariable.php b/core/lib/CloudinaryExtension/Security/CloudinaryEnvironmentVariable.php new file mode 100644 index 0000000..b18209b --- /dev/null +++ b/core/lib/CloudinaryExtension/Security/CloudinaryEnvironmentVariable.php @@ -0,0 +1,47 @@ +environmentVariable = (string)$environmentVariable; + try { + Cloudinary::config_from_url(str_replace('CLOUDINARY_URL=', '', $environmentVariable)); + } catch (\Exception $e){ + throw new \CloudinaryExtension\Exception\InvalidCredentials('Cloudinary config creation from environment variable failed'); + } + } + + public static function fromString($environmentVariable) + { + return new CloudinaryEnvironmentVariable($environmentVariable); + } + + public function getCloud() + { + return Cloud::fromName(Cloudinary::config_get('cloud_name')); + } + + public function getCredentials() + { + return Credentials::fromKeyAndSecret( + Key::fromString(Cloudinary::config_get('api_key')), + Secret::fromString(Cloudinary::config_get('api_secret')) + ); + } + + public function __toString() + { + return $this->environmentVariable; + } + +} \ No newline at end of file diff --git a/core/lib/CloudinaryExtension/Security/ConsoleUrl.php b/core/lib/CloudinaryExtension/Security/ConsoleUrl.php new file mode 100644 index 0000000..e34c900 --- /dev/null +++ b/core/lib/CloudinaryExtension/Security/ConsoleUrl.php @@ -0,0 +1,26 @@ +consoleUrl = self::CLOUDINARY_CONSOLE_BASE_URL . $path; + } + + public static function fromPath($path) + { + return new ConsoleUrl($path); + } + + public function __toString() + { + return $this->consoleUrl; + } +} diff --git a/core/lib/CloudinaryExtension/Security/EnvironmentVariable.php b/core/lib/CloudinaryExtension/Security/EnvironmentVariable.php new file mode 100644 index 0000000..ae51017 --- /dev/null +++ b/core/lib/CloudinaryExtension/Security/EnvironmentVariable.php @@ -0,0 +1,9 @@ +key = (string)$key; + } + + public static function fromString($aKey) + { + return new Key($aKey); + } + + public function __toString() + { + return $this->key; + } + +} diff --git a/core/lib/CloudinaryExtension/Security/Secret.php b/core/lib/CloudinaryExtension/Security/Secret.php new file mode 100644 index 0000000..5a57ef8 --- /dev/null +++ b/core/lib/CloudinaryExtension/Security/Secret.php @@ -0,0 +1,24 @@ +secret = (string)$secret; + } + + public static function fromString($aSecret) + { + return new Secret($aSecret); + } + + public function __toString() + { + return $this->secret; + } +} diff --git a/core/lib/CloudinaryExtension/Security/SignedConsoleUrl.php b/core/lib/CloudinaryExtension/Security/SignedConsoleUrl.php new file mode 100644 index 0000000..dce9b23 --- /dev/null +++ b/core/lib/CloudinaryExtension/Security/SignedConsoleUrl.php @@ -0,0 +1,31 @@ + time(), "mode" => "check"); + $params["signature"] = (string)ApiSignature::fromSecretAndParams($credentials->getSecret(), $params); + $params["api_key"] = (string)$credentials->getKey(); + $query = http_build_query($params); + + $this->signedConsoleUrl = (string)$url . '?' . $query; + } + + public static function fromConsoleUrlAndCredentials(ConsoleUrl $url, Credentials $credentials) + { + return new SignedConsoleUrl($url, $credentials); + } + + public function __toString() + { + return $this->signedConsoleUrl; + } +} diff --git a/core/lib/CloudinaryExtension/SynchroniseAssetsRepositoryInterface.php b/core/lib/CloudinaryExtension/SynchroniseAssetsRepositoryInterface.php new file mode 100644 index 0000000..3c9516d --- /dev/null +++ b/core/lib/CloudinaryExtension/SynchroniseAssetsRepositoryInterface.php @@ -0,0 +1,18 @@ +useFilename = $useFilename; + $this->uniqueFilename = $uniqueFilename; + $this->overwrite = $overwrite; + } + + public static function fromBooleanValues($useFilename, $uniqueFilename, $overwrite) + { + return new UploadConfig($useFilename, $uniqueFilename, $overwrite); + } + + /** + * @return boolean + */ + public function useFilename() + { + return $this->useFilename; + } + + /** + * @return boolean + */ + public function uniqueFilename() + { + return $this->uniqueFilename; + } + + /** + * @return boolean + */ + public function overwrite() + { + return $this->overwrite; + } + + public function toArray() + { + return [ + "use_filename" => $this->useFilename, + "unique_filename" => $this->uniqueFilename, + "overwrite" => $this->overwrite, + ] ; + } +} \ No newline at end of file diff --git a/core/lib/CloudinaryExtension/UploadResponseValidator.php b/core/lib/CloudinaryExtension/UploadResponseValidator.php new file mode 100644 index 0000000..8bdedca --- /dev/null +++ b/core/lib/CloudinaryExtension/UploadResponseValidator.php @@ -0,0 +1,17 @@ +configuration = $configuration; + $this->imageProvider = $imageProvider; + } + + /** + * @param ImageInterface $image + * @param Transformation $transformation + * + * @return string + */ + public function generateFor(ImageInterface $image, Transformation $transformation = null) + { + if ($image instanceof LocalImage) { + return (string)$image; + } + + $transformation = clone ($transformation ?: $this->configuration->getDefaultTransformation()); + + if (in_array($image->getExtension(), $this->configuration->getFormatsToPreserve())) { + $transformation->withoutFormat(); + } + + return (string)$this->imageProvider->retrieveTransformed($image, $transformation); + } + + /** + * @param Image $image + * @param Dimensions $dimensions + * + * @return string + */ + public function generateWithDimensions(ImageInterface $image, Dimensions $dimensions) + { + $transformation = clone $this->configuration->getDefaultTransformation(); + + return $this->generateFor($image, $transformation->withDimensions($dimensions)); + } +} diff --git a/core/lib/CloudinaryExtension/ValidateRemoteUrlRequest.php b/core/lib/CloudinaryExtension/ValidateRemoteUrlRequest.php new file mode 100644 index 0000000..b2c5499 --- /dev/null +++ b/core/lib/CloudinaryExtension/ValidateRemoteUrlRequest.php @@ -0,0 +1,55 @@ +curlHandler = curl_init($url); + $this->setCurlOptions(); + } + + public function validate() + { + $result = $this->execute(); + + if ($result->responseCode == 200 && is_null($result->error)) { + return true; + } + return false; + } + + private function execute() + { + curl_exec($this->curlHandler); + + $result = new \stdClass(); + $result->responseCode = $this->getResponseCode(); + $result->error = $this->getErrorMessage(); + + curl_close($this->curlHandler); + + return $result; + } + + private function getResponseCode() + { + return curl_getinfo($this->curlHandler, CURLINFO_HTTP_CODE); + } + + private function getErrorMessage() + { + return curl_errno($this->curlHandler) ? curl_error($this->curlHandler) : null; + } + + private function setCurlOptions() + { + curl_setopt($this->curlHandler, CURLOPT_HEADER, 1); + curl_setopt($this->curlHandler, CURLOPT_FAILONERROR, 1); + curl_setopt($this->curlHandler, CURLOPT_RETURNTRANSFER, 1); + } +} diff --git a/core/phpspec.yml b/core/phpspec.yml new file mode 100644 index 0000000..d02c98a --- /dev/null +++ b/core/phpspec.yml @@ -0,0 +1,3 @@ +suites: + cloudinary: + src_path: lib diff --git a/phpunit.xml.dist b/core/phpunit.xml.dist old mode 100755 new mode 100644 similarity index 100% rename from phpunit.xml.dist rename to core/phpunit.xml.dist diff --git a/spec/CloudinaryExtension/CloudSpec.php b/core/spec/CloudinaryExtension/CloudSpec.php old mode 100755 new mode 100644 similarity index 100% rename from spec/CloudinaryExtension/CloudSpec.php rename to core/spec/CloudinaryExtension/CloudSpec.php diff --git a/core/spec/CloudinaryExtension/CloudinaryImageProviderSpec.php b/core/spec/CloudinaryExtension/CloudinaryImageProviderSpec.php new file mode 100644 index 0000000..a88030c --- /dev/null +++ b/core/spec/CloudinaryExtension/CloudinaryImageProviderSpec.php @@ -0,0 +1,46 @@ +getKey()->willReturn('apiKey'); + $credentials->getSecret()->willReturn('apiSecret'); + + $configuration->getCloud()->willReturn('testCloud'); + $configuration->getCredentials()->willReturn($credentials); + $configuration->getCdnSubdomainStatus()->willReturn(true); + + $this->beConstructedThrough('fromConfiguration', [ + $configuration, + $configurationBuilder, + $uploadResponseValidator, + $credentialValidator + ]); + } + + function it_sets_user_agent_string(ConfigurationInterface $configuration) + { + $configuration->getUserPlatform()->willReturn('Test User Agent String'); + + $this->getWrappedObject(); + expect(Cloudinary::$USER_PLATFORM)->toBe('Test User Agent String'); + } +} diff --git a/core/spec/CloudinaryExtension/ConfigurationBuilderSpec.php b/core/spec/CloudinaryExtension/ConfigurationBuilderSpec.php new file mode 100644 index 0000000..e51572c --- /dev/null +++ b/core/spec/CloudinaryExtension/ConfigurationBuilderSpec.php @@ -0,0 +1,59 @@ +beConstructedWith($configuration); + + $configuration->getCloud()->willReturn(Cloud::fromName('testCloud')); + $configuration->getCredentials()->willReturn(Credentials::fromKeyAndSecret( + Key::fromString('apiKey'), + Secret::fromString('apiSecret') + )); + $configuration->getDefaultTransformation()->willReturn(Transformation::builder()); + $configuration->getUserPlatform()->willReturn(''); + $configuration->getUploadConfig()->willReturn(UploadConfig::fromBooleanValues(false, false, false)); + } + + function it_should_build_configuration_with_all_values(ConfigurationInterface $configuration) + { + $configuration->getCdnSubdomainStatus()->willReturn(true); + + $expected = [ + 'cloud_name' => 'testCloud', + 'api_key' => 'apiKey', + 'api_secret' => 'apiSecret', + 'cdn_subdomain' => true + ]; + + $this->build()->shouldReturn($expected); + } + + function it_should_build_configuration_with_out_cdn(ConfigurationInterface $configuration) + { + $configuration->getCdnSubdomainStatus()->willReturn(false); + + $expected = [ + 'cloud_name' => 'testCloud', + 'api_key' => 'apiKey', + 'api_secret' => 'apiSecret' + ]; + + $this->build()->shouldReturn($expected); + } +} diff --git a/core/spec/CloudinaryExtension/CredentialValidatorSpec.php b/core/spec/CloudinaryExtension/CredentialValidatorSpec.php new file mode 100644 index 0000000..500ea64 --- /dev/null +++ b/core/spec/CloudinaryExtension/CredentialValidatorSpec.php @@ -0,0 +1,10 @@ +beConstructedThrough('fromKeyAndSecret', [ + Key::fromString($this->key), Secret::fromString($this->secret) + ]); + } + + function it_returns_the_correct_key() + { + $this->getKey()->shouldBeLike($this->key); + } + + function it_returns_the_correct_secret() + { + $this->getSecret()->shouldBeLike($this->secret); + } +} diff --git a/core/spec/CloudinaryExtension/Image/ImageFactorySpec.php b/core/spec/CloudinaryExtension/Image/ImageFactorySpec.php new file mode 100644 index 0000000..bd05b54 --- /dev/null +++ b/core/spec/CloudinaryExtension/Image/ImageFactorySpec.php @@ -0,0 +1,27 @@ +beConstructedWith( + $configuration, + $synchronizable + ); + } + + function it_is_initializable() + { + $this->shouldHaveType('CloudinaryExtension\Image\ImageFactory'); + } +} diff --git a/spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php b/core/spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php old mode 100755 new mode 100644 similarity index 100% rename from spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php rename to core/spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php diff --git a/core/spec/CloudinaryExtension/Image/TransformationSpec.php b/core/spec/CloudinaryExtension/Image/TransformationSpec.php new file mode 100644 index 0000000..73eabe7 --- /dev/null +++ b/core/spec/CloudinaryExtension/Image/TransformationSpec.php @@ -0,0 +1,144 @@ +shouldBeAnInstanceOf('CloudinaryExtension\Image\Transformation'); + } + + function it_overrides_fetch_format_if_provided() + { + $transformationArray = self::builder() + ->withFetchFormat(FetchFormat::fromString('')) + ->build(); + + $transformationArray->offsetGet('fetch_format')->shouldBe(''); + } + + function it_overrides_quality_if_provided() + { + $transformationArray = self::builder() + ->withQuality(Quality::fromString('100')) + ->build(); + + $transformationArray->offsetGet('quality')->shouldBe('100'); + } + + function it_builds_no_dimensions_by_default() + { + $transformationArray = self::builder()->build(); + + $transformationArray->offsetGet('width')->shouldBe(null); + $transformationArray->offsetGet('height')->shouldBe(null); + } + + function it_builds_with_dimensions_when_provided() + { + $transformationArray = self::builder() + ->withDimensions(Dimensions::fromWidthAndHeight('80', '90')) + ->build(); + + $transformationArray->offsetGet('width')->shouldBe(80); + $transformationArray->offsetGet('height')->shouldBe(90); + } + + function it_builds_with_no_gravity_by_default() + { + $transformationArray = self::builder()->build(); + + $transformationArray->offsetGet('gravity')->shouldBe(null); + } + + function it_builds_with_gravity() + { + $transformationArray = self::builder() + ->withGravity(Gravity::fromString('center')) + ->build(); + + $transformationArray->offsetGet('gravity')->shouldBe('center'); + } + + function it_builds_with_crop_set_to_crop_when_gravity_is_set() + { + $transformationArray = self::builder() + ->withGravity(Gravity::fromString('center')) + ->build(); + + $transformationArray->offsetGet('crop')->shouldBe('crop'); + } + + function it_builds_with_crop_set_to_pad_when_gravity_is_not_set() + { + $transformationArray = self::builder()->build(); + + $transformationArray->offsetGet('crop')->shouldBe('pad'); + } + + function it_builds_with_jpeg_format_by_default() + { + $transformationArray = self::builder()->build(); + + $transformationArray->offsetGet('format')->shouldBe('jpg'); + } + + function it_builds_with_format_from_original_extension_when_extension_is_valid() + { + $transformationArray = self::builder() + ->withFormat(Format::fromExtension('png')) + ->build(); + + $transformationArray->offsetGet('format')->shouldBe('png'); + } + + function it_builds_with_jpeg_format_when_original_extension_is_not_valid() + { + $transformationArray = self::builder() + ->withFormat(Format::fromExtension('xpm')) + ->build(); + + $transformationArray->offsetGet('format')->shouldBe('jpg'); + } + + function it_builds_with_image_optimisation_enabled() + { + $transformationArray = self::builder()->build(); + + $transformationArray->offsetGet('fetch_format')->shouldBe('auto'); + } + + function it_builds_with_image_optimisation_disabled() + { + $transformationArray = self::builder()->withOptimisationDisabled()->build(); + + $transformationArray->offsetGet('fetch_format')->shouldBe(''); + } + + function it_builds_with_fetch_format_auto_by_default() + { + $transformationArray = self::builder()->build(); + $transformationArray->offsetGet('fetch_format')->shouldBe('auto'); + } + + function it_handles_flags(){ + $flags = ['1', '2']; + + /** + * @var Transformation $transFormation + */ + $transFormation = self::builder()->addFlags($flags); + $array = $transFormation->build(); + $array->offsetGet('flags')->shouldBe($flags); + } +} diff --git a/spec/CloudinaryExtension/ImageSpec.php b/core/spec/CloudinaryExtension/ImageSpec.php old mode 100755 new mode 100644 similarity index 100% rename from spec/CloudinaryExtension/ImageSpec.php rename to core/spec/CloudinaryExtension/ImageSpec.php diff --git a/core/spec/CloudinaryExtension/Migration/BatchUploaderSpec.php b/core/spec/CloudinaryExtension/Migration/BatchUploaderSpec.php new file mode 100644 index 0000000..474fe1a --- /dev/null +++ b/core/spec/CloudinaryExtension/Migration/BatchUploaderSpec.php @@ -0,0 +1,145 @@ +beConstructedWith($imageProvider, $migrationTask, $logger, self::MEDIA_PATH); + + $image1->tagAsSynchronized()->willReturn(); + $image2->tagAsSynchronized()->willReturn(); + $migrationTask->hasBeenStopped()->willReturn(false, false); + } + + function it_uploads_and_synchronizes_a_collection_of_images( + ImageProvider $imageProvider, + Logger $logger, + Synchronizable $image1, + Synchronizable $image2 + ) { + $path1 = '/z/b/image1.jpg'; + $path2 = '/r/b/image2.jpg'; + $relativePath1 = basename($path1); + $relativePath2 = basename($path2); + $absolutePath1 = self::MEDIA_PATH . $path1; + $absolutePath2 = self::MEDIA_PATH . $path2; + + $image1->getFilename()->willReturn($path1); + $image2->getFilename()->willReturn($path2); + $image1->getRelativePath()->willReturn($relativePath1); + $image2->getRelativePath()->willReturn($relativePath2); + + $images = array($image1, $image2); + + $this->uploadImages($images); + + $imageProvider->upload(Image::fromPath($absolutePath1, $relativePath1))->shouldHaveBeenCalled(); + $imageProvider->upload(Image::fromPath($absolutePath2, $relativePath2))->shouldHaveBeenCalled(); + + $image1->tagAsSynchronized()->shouldHaveBeenCalled(); + $image2->tagAsSynchronized()->shouldHaveBeenCalled(); + + $logger->notice(sprintf(BatchUploader::MESSAGE_UPLOADED, "$absolutePath1 - $relativePath1"))->shouldHaveBeenCalled(); + $logger->notice(sprintf(BatchUploader::MESSAGE_UPLOADED, "$absolutePath2 - $relativePath2"))->shouldHaveBeenCalled(); + + $logger->notice(sprintf(BatchUploader::MESSAGE_STATUS, 2, 0))->shouldHaveBeenCalled(); + } + + function it_logs_an_error_if_any_of_the_image_uploads_fails( + ImageProvider $imageProvider, + Logger $logger, + Synchronizable $image1, + Synchronizable $image2 + ) { + $path1 = '/z/b/image1.jpg'; + $path2 = '/invalid'; + $relativePath1 = basename($path1); + $relativePath2 = basename($path2); + $absolutePath1 = self::MEDIA_PATH . $path1; + $absolutePath2 = self::MEDIA_PATH . $path2; + $apiImage1 = Image::fromPath($absolutePath1, $relativePath1); + $apiImage2 = Image::fromPath($absolutePath2, $relativePath2); + + $image1->getFilename()->willReturn($path1); + $image2->getFilename()->willReturn($path2); + $image1->getRelativePath()->willReturn($relativePath1); + $image2->getRelativePath()->willReturn($relativePath2); + + + $exception = new \Exception('Invalid file'); + + $images = array($image1, $image2); + + $imageProvider->upload($apiImage1)->shouldBeCalled(); + $imageProvider->upload($apiImage2)->willThrow($exception); + + $this->uploadImages($images); + + $image1->tagAsSynchronized()->shouldHaveBeenCalled(); + $image2->tagAsSynchronized()->shouldNotHaveBeenCalled(); + + $logger->error( + sprintf(BatchUploader::MESSAGE_UPLOAD_ERROR, $exception->getMessage(), "$absolutePath2 - $relativePath2") + )->shouldHaveBeenCalled(); + + $logger->notice(sprintf(BatchUploader::MESSAGE_STATUS, 1, 1))->shouldHaveBeenCalled(); + } + + + function it_stops_the_upload_process_if_task_is_stopped( + ImageProvider $imageProvider, + Task $migrationTask, + Logger $logger, + Synchronizable $image1, + Synchronizable $image2 + ) { + $path1 = '/z/b/image1.jpg'; + $path2 = '/invalid'; + $relativePath1 = basename($path1); + $relativePath2 = basename($path2); + $absolutePath1 = self::MEDIA_PATH . $path1; + $absolutePath2 = self::MEDIA_PATH . $path2; + $apiImage1 = Image::fromPath($absolutePath1, $relativePath1); + $apiImage2 = Image::fromPath($absolutePath2, $relativePath2); + + $image1->getFilename()->willReturn($path1); + $image2->getFilename()->willReturn($path2); + $image1->getRelativePath()->willReturn($relativePath1); + $image2->getRelativePath()->willReturn($relativePath2); + + $migrationTask->hasBeenStopped()->willReturn(false, true); + + $images = array($image1, $image2); + + $this->uploadImages($images); + + $imageProvider->upload($apiImage1)->shouldHaveBeenCalled(); + $image1->tagAsSynchronized()->shouldHaveBeenCalled(); + + $imageProvider->upload($apiImage2)->shouldNotHaveBeenCalled(); + $image2->tagAsSynchronized()->shouldNotHaveBeenCalled(); + + $logger->notice(sprintf(BatchUploader::MESSAGE_STATUS, 1, 0))->shouldHaveBeenCalled(); + } +} + diff --git a/spec/CloudinaryExtension/Migration/QueueSpec.php b/core/spec/CloudinaryExtension/Migration/QueueSpec.php old mode 100755 new mode 100644 similarity index 100% rename from spec/CloudinaryExtension/Migration/QueueSpec.php rename to core/spec/CloudinaryExtension/Migration/QueueSpec.php diff --git a/core/spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php b/core/spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php new file mode 100644 index 0000000..2c14797 --- /dev/null +++ b/core/spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php @@ -0,0 +1,31 @@ +beConstructedThrough('fromString', array('CLOUDINARY_URL=cloudinary://aKey:aSecret@aCloud')); + } + + function it_should_extract_the_cloud_name_from_the_environment_variable() + { + $this->getCloud()->shouldBeLike(Cloud::fromName('aCloud')); + } + + function it_should_extract_the_credentials_from_the_environment_variable() + { + $credentials = Credentials::fromKeyAndSecret(Key::fromString('aKey'), Secret::fromString('aSecret')); + $this->getCredentials()->shouldBeLike($credentials); + } + +} diff --git a/spec/CloudinaryExtension/Security/KeySpec.php b/core/spec/CloudinaryExtension/Security/KeySpec.php old mode 100755 new mode 100644 similarity index 100% rename from spec/CloudinaryExtension/Security/KeySpec.php rename to core/spec/CloudinaryExtension/Security/KeySpec.php diff --git a/spec/CloudinaryExtension/Security/SecretSpec.php b/core/spec/CloudinaryExtension/Security/SecretSpec.php old mode 100755 new mode 100644 similarity index 100% rename from spec/CloudinaryExtension/Security/SecretSpec.php rename to core/spec/CloudinaryExtension/Security/SecretSpec.php diff --git a/core/spec/CloudinaryExtension/UploadResponseValidatorSpec.php b/core/spec/CloudinaryExtension/UploadResponseValidatorSpec.php new file mode 100644 index 0000000..ea9aa26 --- /dev/null +++ b/core/spec/CloudinaryExtension/UploadResponseValidatorSpec.php @@ -0,0 +1,25 @@ + 0, 'test' => 'data']; + + $this->validateResponse($image, $response)->shouldReturn($response); + } + + function it_should_return_throw_exception_if_the_image_already_exists(Image $image) + { + $response = ['existing' => 1, 'test' => 'data']; + + $this->shouldThrow('CloudinaryExtension\Exception\MigrationError') + ->duringValidateResponse($image, $response); + } +} diff --git a/core/spec/CloudinaryExtension/UrlGeneratorSpec.php b/core/spec/CloudinaryExtension/UrlGeneratorSpec.php new file mode 100644 index 0000000..f41f0d0 --- /dev/null +++ b/core/spec/CloudinaryExtension/UrlGeneratorSpec.php @@ -0,0 +1,78 @@ +getFormatsToPreserve()->willReturn(['jpg', 'png']); + + $this->beConstructedWith($configuration, $imageProvider); + } + + function it_generates_url_from_given_path_and_transformation( + Image $image, + ImageProvider $imageProvider + ) + { + $transformation = Transformation::builder(); + $image->getExtension()->willReturn('gif'); + + $this->generateFor($image, $transformation); + + $imageProvider->retrieveTransformed($image, $transformation)->shouldHaveBeenCalled(); + } + + function it_removes_image_format_if_its_in_list_of_formats_to_preserve( + Image $image, + ImageProvider $imageProvider + ) + { + $transformation = Transformation::builder(); + $image->getExtension()->willReturn('jpg'); + + $this->generateFor($image, $transformation); + + $imageProvider->retrieveTransformed($image, $transformation->withoutFormat())->shouldHaveBeenCalled(); + } + + function it_generates_url_from_given_path_when_no_transformation_given( + Image $image, + ImageProvider $imageProvider, + ConfigurationInterface $configuration + ) + { + $transformation = Transformation::builder(); + $configuration->getDefaultTransformation()->willReturn($transformation); + + $image->getExtension()->willReturn('gif'); + + $this->generateFor($image); + + $imageProvider->retrieveTransformed($image, $transformation)->shouldHaveBeenCalled(); + } + + function it_does_not_modify_the_transformation_passed(Image $image) + { + $transformation = Transformation::builder(); + $image->getExtension()->willReturn('jpg'); + + $this->generateFor($image, $transformation); + + expect($transformation)->toBeLike(Transformation::builder()); + } +} diff --git a/.gitignore b/magento1/.gitignore similarity index 100% rename from .gitignore rename to magento1/.gitignore diff --git a/.htaccess b/magento1/.htaccess similarity index 100% rename from .htaccess rename to magento1/.htaccess diff --git a/CHANGELOG.md b/magento1/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to magento1/CHANGELOG.md diff --git a/magento1/INSTALL.md b/magento1/INSTALL.md new file mode 100755 index 0000000..cffadb6 --- /dev/null +++ b/magento1/INSTALL.md @@ -0,0 +1,44 @@ +# INSTALLATION + +## Composer + +To install the Cloudinary extenstion via composer you'll need to specify the release you want to install, and the path for the extension repository. You'll also need to specify the path for the `magento-composer-installer` composer plugin. + +The following example of what to add to a `composer.json` file, in order to install via composer, assumes that Magento resides inside a folder named `public/` inside your codebase: + +```JSON +{ + "require": { + "inviqa/cloudinary": "dev-master" + }, + "repositories": [ + { + "type": "vcs", + "url": "git@github.com:inviqa/cloudinary.git" + }, + { + "type": "vcs", + "url": "https://github.com/magento-hackathon/magento-composer-installer" + } + ], + "extra":{ + "magento-root-dir": "./public", + "magento-deploystrategy": "copy" + }, + "autoload": { + "psr-0": { + "": [ + "public/app/code/local", + "public/app/code/community", + "public/app/code/core", + "public/app", + "public/lib" + ], + "Mage" : "public/app/code/core" + } + } +} +``` + +Although the `master` branch should always be stable, for compatibility reasons you probably want to change `dev-master` to a specific release, making sure the extension will only be upgraded to a newer version when explicitly changed in the `composer.json` file. +At the time of writing (December 2014) the current version is `0.1.1` and this should be used in place of `dev-master`. diff --git a/magento1/LICENSE b/magento1/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/magento1/README.md b/magento1/README.md new file mode 100755 index 0000000..31acd9a --- /dev/null +++ b/magento1/README.md @@ -0,0 +1,69 @@ +# Cloudinary Extension + +Cloudinary Magento extension. + +## IMPORTANT INFORMATION + +Currently the extension doesn't cope well with changing clouds when the extension is in use because of the image synchronisation between Magento and Cloudinary. +Changing clouds will cause Magento to be unaware that the images that were already synchronised are now not available in the newly specified cloud. If the records for the synchronisation are removed, then it is possible to run the migration process for the new cloud, but if the configuration ever reverts to the previous cloud, there will be no record of the previously synchronised images, and, in addition to having to reset the synchronisation records to be able to run the migration again, the images will be re-uploaded and exist duplicated on cloudinary. + + +## Functionality Overview + +At the current time (December 2014), the extension is replacing the basic Magento image functionality and providing it via Cloudinary. This allows for on-the-fly image optimisation for specific web clients, and CDN-like capabilities, thus moving the traffic away from the servers as well as serving it from the Cloudinary high performing and geo-location aware network. + +### Image Upload + +From an admin perspective, Image upload is in no way different from the standard Magento image upload. The only change is that when the extension is enabled, images are uploaded to Cloudinary. + +### Image Display + +When the extension is enabled and the image is available in Cloudinary, the images served from the Cloudinary network rather than from the local infrastructure. From the user's perspective, there's no difference in behaviour other than the potential performance gains. + +### Credentials and cloud configuration + +The `key`, `secret` and `cloud` configuration are all available under the `System->Configuration` menu option, in the `Services` group under the name `Cloudinary`. + +### Image Migration + +The Cloudinary extension provides functionality to trigger the upload of pre-existing images to Cloudinary. This process is throttled to prevent network flooding, and is controlled manually to allow for store admins to choose when it should happen. +To start the migration process go to the `Cloudinary->Manage` menu in Magento's admin panel, and press the `Start Migration` button, note that if there are no images to migrate, the button will be greyed out. Pressing the `Start Migration` button (when it's not greyed out), will trigger the migration process and show the migration progress. When the migration finished, the `Start Migration` button will become greyed out. +It's possible to pause the migration process by pressing the `Stop Migration` button. This will allow you to continue the migration process later. +Images become available via Cloudinary as soon as they've been uploaded, so stopping the migration process still allows the site to benefit from Cloudinary for the images that were already uploaded. + +### Enabling/Disabling the extension + +The extension can be enabled and disabled at will. To disable the extension, go to the `Cloudinary->Manage` menu, and press the `Enable Cloudinary` \ `Disable Cloudinary` button. Keep in mind that when the extension is disabled, no images will be served from Cloudinary nor will new images be uploaded to Cloudinary. +If the extension is disabled at any point, it's advisable to start the migration process after enabling it. The `Start Migration` will be greyed out, if there are no images to migrate. + + +## Enabling/Disabling +The enable/disable button in the Cloudinary admin section determines whether the application will request its media from the remote filesystem (Cloudinary) or from the local server. Since the media will end up being store both locally and remotely, the extension can be enabled/disabled without a major impact on the systems operation. It can be done during/before/after migration. + +For example, when extension is *enabled*: +- If the image has already been uploaded to Cloudinary, the system will fetch the image from Cloudinary. +- If the has not yet been uploaded, the system will fetch it locally + +When extension is *disabled* +- The system will always fetch the image locally, regardless of whether it has been uploaded to Cloudinary or no. + +## Known Issues + +- When the migration is started, all existing media will gradually be uploaded to cloudinary. If the extension cannot upload an image (e.g. its missing, corrupted or is rejected by the remote service) it will *not* mark the image as having been migrated (syncrhonized) and will log an error message to system.log, the said image will thus not be removed from the queue of images to migrate and the migration will never complete. It is up to the *Integrator* to be aware of images that could not be uploaded and to decide if they should be deleted from the local database. The migration will only be marked as completed when all of the images in the media gallery have been successfully uploaded. + +## Running Gherkin Features +- Start phantomjs on the VM: +``` +phantomjs --webdriver 4444 --load-images=no & +``` +- Make sure you are in the cloudinary directory: +``` +cd /vagrant/vendor/inviqa/cloudinary +``` +- Run Behat +``` +bin/behat -fprogress +``` + +## API Version +This module currently uses version 1.1.* of the Cloudinary Api diff --git a/behat.yml b/magento1/behat.yml similarity index 100% rename from behat.yml rename to magento1/behat.yml diff --git a/composer.json b/magento1/composer.json similarity index 93% rename from composer.json rename to magento1/composer.json index 6b488a3..3b8266d 100755 --- a/composer.json +++ b/magento1/composer.json @@ -5,9 +5,9 @@ "description": "Cloudinary Magento Integration.", "require": { "php": ">=5.4.0", - "cloudinary/cloudinary_php": "~1.6.0", - "inviqa/cloudinary-core": "~1.1.0", - "inviqa/cloudinary-m1-testcard": "~1.1.1" + "cloudinary/cloudinary_php": "1.6.2", + "inviqa/cloudinary-core": "1.3.1", + "inviqa/cloudinary-m1-testcard": "1.1.2" }, "require-dev": { "phpspec/phpspec": "2.1.0-RC1", diff --git a/composer.lock b/magento1/composer.lock similarity index 97% rename from composer.lock rename to magento1/composer.lock index 4b3e582..96bed97 100755 --- a/composer.lock +++ b/magento1/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "859d806db18164a306d47e49657e3114", - "content-hash": "439eeda0b48d168d7b0aa8fa8d595174", + "hash": "23dc88dfbdefa71f1eee1733ddde3896", + "content-hash": "40bb986bab834c8310f9e5acdf6e99ca", "packages": [ { "name": "cloudinary/cloudinary_php", @@ -163,16 +163,16 @@ }, { "name": "inviqa/cloudinary-core", - "version": "1.1.0", + "version": "1.3.1", "source": { "type": "git", "url": "git@github.com:inviqa/cloudinary-core.git", - "reference": "46a05bb73c6d597ebb96a7609034c3d35300e00d" + "reference": "870367032dd84827f9ddde095caf4ef922f80fc7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/inviqa/cloudinary-core/zipball/46a05bb73c6d597ebb96a7609034c3d35300e00d", - "reference": "46a05bb73c6d597ebb96a7609034c3d35300e00d", + "url": "https://api.github.com/repos/inviqa/cloudinary-core/zipball/870367032dd84827f9ddde095caf4ef922f80fc7", + "reference": "870367032dd84827f9ddde095caf4ef922f80fc7", "shasum": "" }, "require": { @@ -203,10 +203,10 @@ ], "description": "Cloudinary Core.", "support": { - "source": "https://github.com/inviqa/cloudinary-core/tree/1.1.0", + "source": "https://github.com/inviqa/cloudinary-core/tree/1.3.1", "issues": "https://github.com/inviqa/cloudinary-core/issues" }, - "time": "2017-03-16 14:47:52" + "time": "2017-04-24 11:07:11" }, { "name": "inviqa/cloudinary-m1-testcard", @@ -461,16 +461,16 @@ }, { "name": "symfony/console", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa" + "reference": "86407ff20855a5eaa2a7219bd815e9c40a88633e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/81508e6fac4476771275a3f4f53c3fee9b956bfa", - "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa", + "url": "https://api.github.com/repos/symfony/console/zipball/86407ff20855a5eaa2a7219bd815e9c40a88633e", + "reference": "86407ff20855a5eaa2a7219bd815e9c40a88633e", "shasum": "" }, "require": { @@ -518,7 +518,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-03-04 11:00:12" + "time": "2017-04-03 20:37:06" }, { "name": "symfony/debug", @@ -1066,25 +1066,29 @@ }, { "name": "behat/transliterator", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/Behat/Transliterator.git", - "reference": "868e05be3a9f25ba6424c2dd4849567f50715003" + "reference": "826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003", - "reference": "868e05be3a9f25ba6424c2dd4849567f50715003", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c", + "reference": "826ce7e9c2a6664c0d1f381cbb38b1fb80a7ee2c", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "chuyskywalker/rolling-curl": "^3.1", + "php-yaoi/php-yaoi": "^1.0" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -1102,7 +1106,7 @@ "slug", "transliterator" ], - "time": "2015-09-28 16:26:35" + "time": "2017-04-04 11:38:05" }, { "name": "bossa/phpspec2-expect", @@ -2478,21 +2482,24 @@ }, { "name": "react/promise", - "version": "v2.5.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "2760f3898b7e931aa71153852dcd48a75c9b95db" + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/2760f3898b7e931aa71153852dcd48a75c9b95db", - "reference": "2760f3898b7e931aa71153852dcd48a75c9b95db", + "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", "shasum": "" }, "require": { "php": ">=5.4.0" }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, "type": "library", "autoload": { "psr-4": { @@ -2517,7 +2524,7 @@ "promise", "promises" ], - "time": "2016-12-22 14:09:01" + "time": "2017-03-25 12:08:31" }, { "name": "sebastian/exporter", @@ -2953,7 +2960,7 @@ }, { "name": "symfony/browser-kit", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", @@ -3010,7 +3017,7 @@ }, { "name": "symfony/class-loader", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -3063,16 +3070,16 @@ }, { "name": "symfony/config", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2" + "reference": "35b7dfa089d7605eb1fdd46281b3070fb9f38750" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/06ce6bb46c24963ec09323da45d0f4f85d3cecd2", - "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2", + "url": "https://api.github.com/repos/symfony/config/zipball/35b7dfa089d7605eb1fdd46281b3070fb9f38750", + "reference": "35b7dfa089d7605eb1fdd46281b3070fb9f38750", "shasum": "" }, "require": { @@ -3115,11 +3122,11 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-03-01 18:13:50" + "time": "2017-04-04 15:24:26" }, { "name": "symfony/css-selector", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -3232,7 +3239,7 @@ }, { "name": "symfony/dom-crawler", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", @@ -3288,16 +3295,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0" + "reference": "88b65f0ac25355090e524aba4ceb066025df8bd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bb4ec47e8e109c1c1172145732d0aa468d967cd0", - "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/88b65f0ac25355090e524aba4ceb066025df8bd2", + "reference": "88b65f0ac25355090e524aba4ceb066025df8bd2", "shasum": "" }, "require": { @@ -3344,7 +3351,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-02-21 08:33:48" + "time": "2017-04-03 20:37:06" }, { "name": "symfony/filesystem", @@ -3397,16 +3404,16 @@ }, { "name": "symfony/finder", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5fc4b5cab38b9d28be318fcffd8066988e7d9451" + "reference": "7131327eb95d86d72039fd1216226c28f36fd02a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5fc4b5cab38b9d28be318fcffd8066988e7d9451", - "reference": "5fc4b5cab38b9d28be318fcffd8066988e7d9451", + "url": "https://api.github.com/repos/symfony/finder/zipball/7131327eb95d86d72039fd1216226c28f36fd02a", + "reference": "7131327eb95d86d72039fd1216226c28f36fd02a", "shasum": "" }, "require": { @@ -3442,7 +3449,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-02-21 08:33:48" + "time": "2017-03-20 08:46:40" }, { "name": "symfony/polyfill-apcu", @@ -3499,7 +3506,7 @@ }, { "name": "symfony/process", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -3548,16 +3555,16 @@ }, { "name": "symfony/translation", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "b538355bc99db2ec7cc35284ec76d92ae7d1d256" + "reference": "047e97a64d609778cadfc76e3a09793696bb19f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/b538355bc99db2ec7cc35284ec76d92ae7d1d256", - "reference": "b538355bc99db2ec7cc35284ec76d92ae7d1d256", + "url": "https://api.github.com/repos/symfony/translation/zipball/047e97a64d609778cadfc76e3a09793696bb19f1", + "reference": "047e97a64d609778cadfc76e3a09793696bb19f1", "shasum": "" }, "require": { @@ -3608,20 +3615,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-03-04 12:20:59" + "time": "2017-03-21 21:39:01" }, { "name": "symfony/yaml", - "version": "v2.8.18", + "version": "v2.8.19", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d" + "reference": "286d84891690b0e2515874717e49360d1c98a703" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d", - "reference": "2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/286d84891690b0e2515874717e49360d1c98a703", + "reference": "286d84891690b0e2515874717e49360d1c98a703", "shasum": "" }, "require": { @@ -3657,7 +3664,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-03-01 18:13:50" + "time": "2017-03-20 09:41:44" }, { "name": "theseer/directoryscanner", @@ -3703,16 +3710,16 @@ }, { "name": "theseer/fdomdocument", - "version": "1.6.1", + "version": "1.6.5", "source": { "type": "git", "url": "https://github.com/theseer/fDOMDocument.git", - "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684" + "reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", - "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", + "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/8dcfd392135a5bd938c3c83ea71419501ad9855d", + "reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d", "shasum": "" }, "require": { @@ -3739,7 +3746,7 @@ ], "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2015-05-27 22:58:02" + "time": "2017-04-21 14:50:31" }, { "name": "theseer/fxsl", diff --git a/magento1/features/admin/delete_image.feature b/magento1/features/admin/delete_image.feature new file mode 100755 index 0000000..6146042 --- /dev/null +++ b/magento1/features/admin/delete_image.feature @@ -0,0 +1,9 @@ +Feature: Deleting images from the image provider + In order to keep the image gallery content relevant + As a store admin + I want to be able to delete images from the image provider + + Scenario: Administrator deletes image from image provider + Given the image provider has an image "blue-shirt.jpg" + When I delete the "blue-shirt.jpg" image + Then the image "blue-shirt.jpg" should no longer be available in the image provider \ No newline at end of file diff --git a/magento1/features/admin/extension_disable_enable.feature b/magento1/features/admin/extension_disable_enable.feature new file mode 100755 index 0000000..5e3f27d --- /dev/null +++ b/magento1/features/admin/extension_disable_enable.feature @@ -0,0 +1,22 @@ +@not-automated +Feature: Enabling and disabling the Cloudinary extension + + As an Integrator + + + Scenario: Integrator enables the extension but image has not been migrated + Given the cloudinary media gallery contains the image "lolcat.png" + And this image has not yet been migrated to cloudinary + When the integrator enables the module + Then the image should be provided locally + + Scenario: Integrator enables the extension and image has been migrated + Given the cloudinary media gallery contains the image "lolcat.png" + And this image has already been migrated to cloudinary + When the integrator enables the module + Then the image should be provided by cloudinary + + Scenario: Integrator disables the extension + Given the cloudinary media gallery contains the image "lolcat.png" + When the integrator disables the module + Then the image should be provided locally \ No newline at end of file diff --git a/magento1/features/admin/prompted_to_sign_up_to_cloudinary.feature b/magento1/features/admin/prompted_to_sign_up_to_cloudinary.feature new file mode 100755 index 0000000..6661f3e --- /dev/null +++ b/magento1/features/admin/prompted_to_sign_up_to_cloudinary.feature @@ -0,0 +1,16 @@ +Feature: Admin is prompted to sign up to Cloudinary + In order to register for a Cloudinary account after installing the extension + As a store admin + I should be prompted to sign up to Cloudinary + + @javascript @ui + Scenario: Being prompted to sign up to Cloudinary + Given I have not configured my environment variable + When I go to the Cloudinary configuration + Then I should be prompted to sign up to Cloudinary + + @javascript @ui + Scenario: Not being prompted to sign up to Cloudinary + Given I have configured my environment variable + When I go to the Cloudinary configuration + Then I should not be prompted to sign up to Cloudinary \ No newline at end of file diff --git a/features/bootstrap/Domain/ConfigurationContext.php b/magento1/features/bootstrap/Domain/ConfigurationContext.php similarity index 100% rename from features/bootstrap/Domain/ConfigurationContext.php rename to magento1/features/bootstrap/Domain/ConfigurationContext.php diff --git a/features/bootstrap/Domain/DeleteImageDomainContext.php b/magento1/features/bootstrap/Domain/DeleteImageDomainContext.php similarity index 100% rename from features/bootstrap/Domain/DeleteImageDomainContext.php rename to magento1/features/bootstrap/Domain/DeleteImageDomainContext.php diff --git a/features/bootstrap/Domain/DomainContext.php b/magento1/features/bootstrap/Domain/DomainContext.php similarity index 100% rename from features/bootstrap/Domain/DomainContext.php rename to magento1/features/bootstrap/Domain/DomainContext.php diff --git a/features/bootstrap/Domain/TransformationContext.php b/magento1/features/bootstrap/Domain/TransformationContext.php similarity index 100% rename from features/bootstrap/Domain/TransformationContext.php rename to magento1/features/bootstrap/Domain/TransformationContext.php diff --git a/magento1/features/bootstrap/EndToEnd/CloudinaryConfig.php b/magento1/features/bootstrap/EndToEnd/CloudinaryConfig.php new file mode 100644 index 0000000..6061368 --- /dev/null +++ b/magento1/features/bootstrap/EndToEnd/CloudinaryConfig.php @@ -0,0 +1,61 @@ +getMagentoFacade()->setConfigEncrypted(Configuration::CONFIG_PATH_ENVIRONMENT_VARIABLE, $env); + } + + /** + * @Given the Cloudinary module integration is enabled + */ + public function theCloudinaryModuleIntegrationIsEnabled() + { + $this->getMagentoFacade()->setConfig( + Configuration::CONFIG_PATH_ENABLED, + Configuration::STATUS_ENABLED + ); + } + + /** + * @Given the Cloudinary module foldered mode is active + */ + public function theCloudinaryModuleFolderedModeIsActive() + { + $this->getMagentoFacade()->setConfig( + Configuration::CONFIG_FOLDERED_MIGRATION, + Configuration::STATUS_ENABLED + ); + } + + /** + * @Given the Cloudinary module foldered mode is inactive + */ + public function theCloudinaryModuleFolderedModeIsInactive() + { + $this->getMagentoFacade()->setConfig( + Configuration::CONFIG_FOLDERED_MIGRATION, + Configuration::STATUS_DISABLED + ); + } +} diff --git a/magento1/features/bootstrap/EndToEnd/Context.php b/magento1/features/bootstrap/EndToEnd/Context.php new file mode 100755 index 0000000..00416e8 --- /dev/null +++ b/magento1/features/bootstrap/EndToEnd/Context.php @@ -0,0 +1,241 @@ +parameters = $parameters; + $this->cloudinaryConsole = new CloudinaryConsole($parameters['cloudinary_env']); + $this->magentoFacade = new MagentoFacade(); + } + + /** + * @param FixtureManager $fixtureManager + */ + protected function setFixtureManager(FixtureManager $fixtureManager) + { + $this->fixtureManager = $fixtureManager; + } + + /** + * @return FixtureManager + */ + protected function getFixtureManager() + { + return $this->fixtureManager; + } + + /** + * @return array + */ + protected function getParameters() + { + return $this->parameters; + } + + /** + * @return CloudinaryConsole + */ + protected function getCloudinaryConsole() + { + return $this->cloudinaryConsole; + } + + /** + * @return MagentoFacade + */ + protected function getMagentoFacade() + { + return $this->magentoFacade; + } + + /** + * @When I wait for a keypress + */ + public function iWaitForAKeypress() + { + fread(STDIN, 1); + } + + /** + * @When image :arg1 is added to product :arg2 + */ + public function imageIsAddedToProduct($imageName, $productSku) + { + $this->getMagentoFacade()->addImageToProductWithSku( + $productSku, + $this->getFixtureFilePath($imageName) + ); + } + + /** + * @When I delete the images from product :arg1 + */ + public function iDeleteTheImagesFromProduct($productSku) + { + $this->getMagentoFacade()->deleteImagesFromProductWithSku($productSku); + } + + /** + * @Then the image for product :arg1 can be seen in the image provider root folder + */ + public function theImageForProductCanBeSeenInTheImageProviderRootFolder($sku) + { + $imagePath = $this->getMagentoFacade()->productWithSku($sku)->getImage(); + + $details = $this->getCloudinaryConsole()->detailsForImagePath($this->nameWithoutExtensionFromPath($imagePath)); + + expect($details['public_id'])->shouldBeEqualTo($this->nameWithoutExtensionFromPath($imagePath)); + } + + /** + * @Then the image can be seen on the image provider in the correct folder for product :arg1 + */ + public function theImageCanBeSeenOnTheImageProviderInTheCorrectFolderForProduct($sku) + { + $imagePath = $this->getMagentoFacade()->productWithSku($sku)->getImage(); + + $folderedPath = sprintf('media/catalog/product%s', $this->nameAndPathWithoutExtension($imagePath)); + + $details = $this->getCloudinaryConsole()->detailsForImagePath($folderedPath); + + expect($details['public_id'])->shouldBeEqualTo($folderedPath); + } + + /** + * @Given the product :arg1 has an image :arg2 on the image provider + */ + public function theProductHasAnImageOnTheImageProvider($productSku, $imageName) + { + $this->imageIsAddedToProduct($imageName, $productSku); + $this->productImagePathFromPreviousStep = $this->getMagentoFacade()->imagePathForProductWithSku($productSku); + $this->theImageForProductCanBeSeenInTheImageProviderRootFolder($productSku); + } + + /** + * @Given the image provider has an image :arg1 in the correct folder for product :arg2 + */ + public function theImageProviderHasAnImageInTheCorrectFolderForProduct($imageName, $productSku) + { + $this->imageIsAddedToProduct($imageName, $productSku); + $this->productImagePathFromPreviousStep = $this->getMagentoFacade()->imagePathForProductWithSku($productSku); + $this->theImageCanBeSeenOnTheImageProviderInTheCorrectFolderForProduct($productSku); + } + + /** + * @Then there are no images for the :arg1 product in the image provider root folder + */ + public function thereAreNoImagesForTheProductInTheImageProviderRootFolder($productSku) + { + try { + $details = $this->getCloudinaryConsole()->detailsForImagePath( + $this->nameWithoutExtensionFromPath($this->productImagePathFromPreviousStep) + ); + + throw new \Exception( + sprintf( + 'Expected nothing but found images for product image path: %s %s', + $this->productImagePathFromPreviousStep, + getenv('BEHAT_DEBUG') ? json_encode($details) : '' + ) + ); + } + + catch (\Cloudinary\Api\NotFound $e) {} + } + + /** + * @Then the image can not be seen on the image provider in the correct folder for product :arg1 + */ + public function theImageCanNotBeSeenOnTheImageProviderInTheCorrectFolderForProduct($productSku) + { + $folderedPath = sprintf( + 'media/catalog/product%s', + $this->nameAndPathWithoutExtension($this->productImagePathFromPreviousStep) + ); + + try { + $details = $this->getCloudinaryConsole()->detailsForImagePath($folderedPath); + + throw new \Exception( + sprintf( + 'Expected nothing but found images for product image path: %s %s', + $folderedPath, + getenv('BEHAT_DEBUG') ? json_encode($details) : '' + ) + ); + } + + catch (\Cloudinary\Api\NotFound $e) {} + } + + /** + * @Given the image provider has no images + */ + public function theImageProviderHasNoImages() + { + $this->getCloudinaryConsole()->deleteAll(); + } + + /** + * @param string $path + * @return string + */ + private function nameWithoutExtensionFromPath($path) + { + $info = pathinfo($path); + return $info['filename']; + } + + /** + * @param string $path + * @return string + */ + private function nameAndPathWithoutExtension($path) + { + $info = pathinfo($path); + return sprintf('%s%s%s', $info['dirname'], $info['dirname'] ? '/' : '', $info['filename']); + } +} diff --git a/magento1/features/bootstrap/EndToEnd/Fixture.php b/magento1/features/bootstrap/EndToEnd/Fixture.php new file mode 100644 index 0000000..cf7dccb --- /dev/null +++ b/magento1/features/bootstrap/EndToEnd/Fixture.php @@ -0,0 +1,65 @@ +setFixtureManager(new FixtureManager(new YamlProvider())); + } + + /** + * @AfterScenario + */ + public function afterScenario() + { + $this->getFixtureManager()->clear(); + } + + /** + * @Given the product :arg1 exists + */ + public function theProductExists($sku, $retry = true) + { + try { + $this->getFixtureManager()->loadFixture('catalog/product', __DIR__ . '/../Fixtures/' . $sku . '.yaml'); + } + + catch (\Zend_Db_Statement_Exception $e) { + if (!$retry) { + throw $e; + } + $product = Mage::getModel('catalog/product'); + $product->load($product->getIdBySku($sku)); + $product->delete(); + $this->theProductExists($sku, false); + } + + if (getenv('BEHAT_DEBUG')) { + echo 'Fixture product = ', $this->getMagentoFacade()->productWithSku($sku)->getId(); + } + } + + protected function getFixtureFilePath($filename) + { + $path = realpath(__DIR__ . '/../Fixtures/' . $filename); + + if (!file_exists($path)) { + throw new Exception('Fixture file not found: ' . $path); + } + + return $path; + } +} diff --git a/magento1/features/bootstrap/Facade/CloudinaryConsole.php b/magento1/features/bootstrap/Facade/CloudinaryConsole.php new file mode 100644 index 0000000..3992d20 --- /dev/null +++ b/magento1/features/bootstrap/Facade/CloudinaryConsole.php @@ -0,0 +1,33 @@ +cloudinaryEnvironmentVariable = $cloudinaryEnvironmentVariable; + } + + public function detailsForImagePath($imagePath) + { + if (getenv('BEHAT_DEBUG')) { + echo sprintf('Fetching details for image path: %s%s', $imagePath, PHP_EOL); + } + + $api = new Api(); + return $api->resource($imagePath); + } + + public function deleteAll() + { + CloudinaryEnvironmentVariable::fromString($this->cloudinaryEnvironmentVariable); + $api = new Api(); + $api->delete_all_resources(); + } +} diff --git a/magento1/features/bootstrap/Facade/Magento.php b/magento1/features/bootstrap/Facade/Magento.php new file mode 100644 index 0000000..02bde98 --- /dev/null +++ b/magento1/features/bootstrap/Facade/Magento.php @@ -0,0 +1,94 @@ +load($product->getIdBySku($sku)); + + if (!$product->getId()) { + throw new \Exception('Cannot find product with sku: ' . $sku); + } + + return $product; + } + + /** + * @param string $sku + * @return string + * @throws \Exception + */ + public function imagePathForProductWithSku($sku) + { + return $this->productWithSku($sku)->getImage(); + } + + /** + * @param string $sku + * @param string $imagePath + * @throws \Exception + */ + public function addImageToProductWithSku($sku, $imagePath) + { + Mage::app('default', 'store')->setCurrentStore(\Mage_Core_Model_App::ADMIN_STORE_ID); + $product = $this->productWithSku($sku); + $product->addImageToMediaGallery($imagePath, ['image', 'thumbnail', 'small_image'], false, false); + $product->save(); + } + + /** + * @param string $sku + */ + public function deleteImagesFromProductWithSku($sku) + { + Mage::app('default', 'store')->setCurrentStore(\Mage_Core_Model_App::ADMIN_STORE_ID); + $product = $this->productWithSku($sku); + + $galleryAttribute = \Mage::getModel('catalog/resource_eav_attribute') + ->loadByCode($product->getEntityTypeId(), 'media_gallery'); + + foreach ($product->getMediaGalleryImages() as $image) { + $galleryAttribute->getBackend()->removeImage($product, $image->getFile()); + } + + $product->save(); + } + + /** + * @param string $path + * @param string $value + */ + public function setConfig($path, $value) + { + Mage::app('default', 'store')->setCurrentStore(\Mage_Core_Model_App::ADMIN_STORE_ID); + Mage::getConfig()->saveConfig($path, $value)->reinit(); + Mage::app()->getStore()->setConfig($path, $value); + if (getenv('BEHAT_DEBUG')) { + echo sprintf('Set config path: %s with value: %s%s', $path, $value, PHP_EOL); + } + } + + /** + * @param string $path + * @param string $value + */ + public function setConfigEncrypted($path, $value) + { + Mage::app('default', 'store')->setCurrentStore(\Mage_Core_Model_App::ADMIN_STORE_ID); + Mage::getConfig()->saveConfig($path, Mage::helper('core')->encrypt($value))->reinit(); + Mage::app()->getStore()->setConfig($path, Mage::helper('core')->encrypt($value)); + if (getenv('BEHAT_DEBUG')) { + echo sprintf('Set config path: %s with encrypted value of: %s%s', $path, $value, PHP_EOL); + } + } +} diff --git a/magento1/features/bootstrap/Fixtures/Admin.yaml b/magento1/features/bootstrap/Fixtures/Admin.yaml new file mode 100755 index 0000000..4ddfaf2 --- /dev/null +++ b/magento1/features/bootstrap/Fixtures/Admin.yaml @@ -0,0 +1,6 @@ +admin/user: + username: testadmin + firstname: admin + lastname: admin + email: testadmin@example.com + password: testadmin123 diff --git a/magento1/features/bootstrap/Fixtures/Apple.yaml b/magento1/features/bootstrap/Fixtures/Apple.yaml new file mode 100644 index 0000000..70f10ab --- /dev/null +++ b/magento1/features/bootstrap/Fixtures/Apple.yaml @@ -0,0 +1,17 @@ +catalog/product: + sku: Apple + attribute_set_id: 4 + name: product name + weight: 2 + price: 10 + description: Granny Smith + short_description: An apple + tax_class_id: 1 + type_id: simple + visibility: 4 + status: 1 + stock_data: { is_in_stock: 1, qty: 99999 } + website_ids: [1] + media_gallery: + images: [] + diff --git a/magento1/features/bootstrap/Fixtures/apple.jpg b/magento1/features/bootstrap/Fixtures/apple.jpg new file mode 100644 index 0000000..dea4a87 Binary files /dev/null and b/magento1/features/bootstrap/Fixtures/apple.jpg differ diff --git a/features/bootstrap/ImageProviders/ConfigImageProvider.php b/magento1/features/bootstrap/ImageProviders/ConfigImageProvider.php similarity index 100% rename from features/bootstrap/ImageProviders/ConfigImageProvider.php rename to magento1/features/bootstrap/ImageProviders/ConfigImageProvider.php diff --git a/features/bootstrap/ImageProviders/FakeImageProvider.php b/magento1/features/bootstrap/ImageProviders/FakeImageProvider.php similarity index 100% rename from features/bootstrap/ImageProviders/FakeImageProvider.php rename to magento1/features/bootstrap/ImageProviders/FakeImageProvider.php diff --git a/features/bootstrap/ImageProviders/TransformingImageProvider.php b/magento1/features/bootstrap/ImageProviders/TransformingImageProvider.php similarity index 100% rename from features/bootstrap/ImageProviders/TransformingImageProvider.php rename to magento1/features/bootstrap/ImageProviders/TransformingImageProvider.php diff --git a/magento1/features/bootstrap/Page/AdminLogin.php b/magento1/features/bootstrap/Page/AdminLogin.php new file mode 100755 index 0000000..1e55e3f --- /dev/null +++ b/magento1/features/bootstrap/Page/AdminLogin.php @@ -0,0 +1,37 @@ + array('xpath' => '//*[@id="username"]'), + 'Password' => array('xpath' => '//*[@id="login"]'), + 'Login Button' => array('xpath' => '//*[@title="Login"]'), + ); + + public function login($username, $password) + { + $this->getElement('User Name')->setValue($username); + $this->getElement('Password')->setValue($password); + $this->getElement('Login Button')->click(); + } + + public function sessionLogin($username, $password, $mageSession) + { + if (!$this->isOpen()) { + $this->open(); + } + + $this->getSession() + ->setCookie( + 'adminhtml', + $mageSession->adminLogin($username, $password) + ) + ; + } +} diff --git a/magento1/features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php b/magento1/features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php new file mode 100755 index 0000000..abd96a3 --- /dev/null +++ b/magento1/features/bootstrap/Page/CloudinaryAdminSystemConfiguration.php @@ -0,0 +1,41 @@ + array('xpath' => '//*[@id="cloudinary_setup-head"]'), + 'Environment Variable' => array('xpath' => '//*[@id="cloudinary_setup_cloudinary_environment_variable"]'), + 'Save Config' => array('xpath' => '//*[@title="Save Config"]'), + 'Image Transformations Header' => array('xpath' => '//*[@id="cloudinary_transformations-head"]'), + 'Default Gravity for Images' => array('css' => "#cloudinary_transformations_cloudinary_gravity option[selected='selected']"), + 'Sign Up Prompt' => array('xpath' => '//*[@id="config_edit_form"]//h3[@id="cloudinary-signup-prompt"]'), + ); + + public function enterEnvironmentVariable($anEnvironmentVariable) + { + $this->getElement('Setup Header')->click(); + $this->getElement('Environment Variable')->setValue($anEnvironmentVariable); + } + + public function saveCloudinaryConfiguration() + { + $this->getElement('Save Config')->click(); + } + + public function getSelectedGravity() + { + return $this->getElement('Default Gravity for Images')->getHtml(); + } + + public function containsSignUpPrompt() + { + return $this->hasElement('Sign Up Prompt'); + } + +} diff --git a/magento1/features/bootstrap/Page/CloudinaryManagement.php b/magento1/features/bootstrap/Page/CloudinaryManagement.php new file mode 100755 index 0000000..6510c19 --- /dev/null +++ b/magento1/features/bootstrap/Page/CloudinaryManagement.php @@ -0,0 +1,35 @@ + array('css' => 'button[title="Enable Cloudinary"]'), + 'Disable Button' => array('css' => 'button[title="Disable Cloudinary"]'), + ); + + public function enable() + { + $this->getElement('Enable Button')->click(); + } + + public function disable() + { + $this->getElement('Disable Button')->click(); + } + + public function hasDisableButton() + { + return $this->hasElement('Disable Button'); + } + + public function hasEnableButton() + { + return $this->hasElement('Enable Button'); + } +} diff --git a/magento1/features/bootstrap/Ui/AdminCredentialsContext.php b/magento1/features/bootstrap/Ui/AdminCredentialsContext.php new file mode 100755 index 0000000..772b49d --- /dev/null +++ b/magento1/features/bootstrap/Ui/AdminCredentialsContext.php @@ -0,0 +1,196 @@ +adminConfigPage = $adminSystemConfiguration; + $this->adminLoginPage = $adminLoginPage; + } + + /** + * @BeforeScenario + */ + public function beforeScenario() + { + $this->_fixtureManager = new FixtureManager(new YamlProvider()); + $this->_fixtureManager->loadFixture('admin/user', __DIR__ . DS . '../Fixtures/Admin.yaml'); + } + + /** + * @AfterScenario + */ + public function afterScenario() + { + $this->_fixtureManager->clear(); + } + + /** + * @Transform :anImage + */ + public function transformStringToAnImage($string) + { + return Image::fromPath($string); + } + + /** + * @Given I have an image :anImage + */ + public function iHaveAnImage($anImage) + { + $this->image = $anImage; + } + + /** + * @When I upload the image :anImage + */ + public function iUploadTheImage(Image $anImage) + { + $environmentVariable = CloudinaryEnvironmentVariable::fromString('CLOUDINARY_URL=cloudinary://ABC123:DEF456@session-digital'); + $this->saveEnvironmentVariableToMagentoConfiguration($environmentVariable); + + $this->imageProvider = new FakeImageProvider($environmentVariable); + + $this->imageProvider->setMockCloud(Cloud::fromName('session-digital')); + $this->imageProvider->setMockCredentials(Key::fromString('ABC123'), Secret::fromString('DEF456')); + + $this->imageProvider->upload($anImage); + } + + /** + * @Then the image should be available through the image provider + */ + public function theImageShouldBeAvailableThroughTheImageProvider() + { + expect($this->imageProvider->getImageUrlByName((string)$this->image))->notToBe(''); + } + + /** + * @Given I have used a valid environment variable in the configuration + */ + public function iHaveUsedAValidEnvironmentVariableInTheConfiguration() + { + $environmentVariable = CloudinaryEnvironmentVariable::fromString('CLOUDINARY_URL=cloudinary://ABC123:DEF456@session-digital'); + $this->imageProvider = new FakeImageProvider($environmentVariable); + } + + /** + * @Given I have used an invalid environment variable in the configuration + */ + public function iHaveUsedAnInvalidEnvironmentVariableInTheConfiguration() + { + $environmentVariable = CloudinaryEnvironmentVariable::fromString('CLOUDINARY_URL=cloudinary://UVW789:XYZ123@session-digital'); + $this->imageProvider = new FakeImageProvider($environmentVariable); + } + + /** + * @Given I have not configured my environment variable + */ + public function iHaveNotConfiguredMyEnvironmentVariable() + { + $this->saveEnvironmentVariableToMagentoConfiguration(''); + } + + /** + * @Given I have configured my environment variable + */ + public function iHaveConfiguredMyEnvironmentVariable() + { + $this->saveEnvironmentVariableToMagentoConfiguration('anEnvironmentVariable'); + } + + /** + * @When I ask the provider to validate my credentials + */ + public function iAskTheProviderToValidateMyCredentials() + { + $this->imageProvider->setMockCloud(Cloud::fromName('session-digital')); + $this->imageProvider->setMockCredentials(Key::fromString('ABC123'), Secret::fromString('DEF456')); + + $this->areCredentialsValid = $this->imageProvider->validateCredentials(); + } + + /** + * @Then I should be informed my credentials are valid + */ + public function iShouldBeInformedMyCredentialsAreValid() + { + expect($this->areCredentialsValid)->toBe(true); + } + + /** + * @Then I should be informed that my credentials are not valid + */ + public function iShouldBeInformedThatMyCredentialsAreNotValid() + { + expect($this->areCredentialsValid)->toBe(false); + } + + /** + * @Given I have not configured my cloud and credentials + */ + public function iHaveNotConfiguredMyCloudAndCredentials() + { + $this->saveCredentialsAndCloudToMagentoConfiguration('', '', ''); + } + + /** + * @When I go to the Cloudinary configuration + */ + public function iGoToTheCloudinaryConfiguration() + { + $this->adminConfigPage->open(); + } + + /** + * @Then I should be prompted to sign up to Cloudinary + */ + public function iShouldBePromptedToSignUpToCloudinary() + { + expect($this->adminConfigPage->containsSignUpPrompt())->toBe(true); + } + + /** + * @Then I should not be prompted to sign up to Cloudinary + */ + public function iShouldNotBePromptedToSignUpToCloudinary() + { + expect($this->adminConfigPage->containsSignUpPrompt())->toBe(false); + } + + private function saveEnvironmentVariableToMagentoConfiguration($environmentVariable) + { + $this->adminLoginPage->sessionLogin('testadmin', 'testadmin123', $this->getSessionService()); + + $this->adminConfigPage->open(); + + $this->adminConfigPage->enterEnvironmentVariable($environmentVariable); + $this->adminConfigPage->saveCloudinaryConfiguration(); + + } + +} diff --git a/magento1/features/bootstrap/Ui/ModuleEnableContext.php b/magento1/features/bootstrap/Ui/ModuleEnableContext.php new file mode 100755 index 0000000..9c7705f --- /dev/null +++ b/magento1/features/bootstrap/Ui/ModuleEnableContext.php @@ -0,0 +1,76 @@ +adminLogin = $adminLogin; + $this->cloudinaryManagement = $cloudinaryManagement; + } + + /** + * @Given I am logged in as an administrator + */ + public function iAmLoggedInAsAnAdministrator() + { + $this->adminLogin->sessionLogin('testadmin', 'testadmin123', $this->getSessionService()); + } + + /** + * @Given the Cloudinary module is disabled + */ + public function theCloudinaryModuleIsDisabled() + { + \Mage::helper('cloudinary_cloudinary/configuration')->disable(); + } + + /** + * @When I access the Cloudinary configuration + */ + public function iAccessTheCloudinaryConfiguration() + { + $this->cloudinaryManagement->open(); + } + + /** + * @Then I should be able to enable the module + */ + public function iShouldBeAbleToEnableTheModule() + { + $this->cloudinaryManagement->enable(); + + expect($this->cloudinaryManagement)->toHaveDisableButton(); + } + + /** + * @Given the Cloudinary module is enabled + */ + public function theCloudinaryModuleIsEnabled() + { + \Mage::helper('cloudinary_cloudinary/configuration')->enable(); + } + + /** + * @Then I should be able to disable the module + */ + public function iShouldBeAbleToDisableTheModule() + { + $this->cloudinaryManagement->disable(); + + expect($this->cloudinaryManagement)->toHaveEnableButton(); + } +} diff --git a/magento1/features/configuration.feature b/magento1/features/configuration.feature new file mode 100755 index 0000000..6dd37f3 --- /dev/null +++ b/magento1/features/configuration.feature @@ -0,0 +1,9 @@ +Feature: Configuring the image provider + In order to fit the image provider to my needs + As an image provider user + I want to be able to provide configuration to it + + Scenario: Configuring the image provider to use multiple sub-domains + Given I have a configuration to use multiple sub-domains + When I apply the configuration to the image provider + Then the image provider should use multiple sub-domains diff --git a/magento1/features/e2e/delete.feature b/magento1/features/e2e/delete.feature new file mode 100644 index 0000000..f29db10 --- /dev/null +++ b/magento1/features/e2e/delete.feature @@ -0,0 +1,14 @@ +Feature: Removing images from the image provider + In order to keep the image gallery content relevant + As a store admin + I want deleted product images to be removed from the image provider + + @e2e + Scenario: Administrator deletes product image + Given the product "Apple" exists + And the Cloudinary module credentials are set + And the Cloudinary module integration is enabled + And the Cloudinary module foldered mode is inactive + And the product "Apple" has an image "apple.jpg" on the image provider + When I delete the images from product "Apple" + Then there are no images for the "Apple" product in the image provider root folder diff --git a/magento1/features/e2e/foldered.feature b/magento1/features/e2e/foldered.feature new file mode 100644 index 0000000..1294fee --- /dev/null +++ b/magento1/features/e2e/foldered.feature @@ -0,0 +1,23 @@ +Feature: Maintain original image folder path + In order to maintain good SEO and allow identical filenames for different folders + As a store admin + I want the option for the folder path of product images to be mirrored by the image provider + + @e2e + Scenario: Administrator adds image to product + Given the product "Apple" exists + And the Cloudinary module credentials are set + And the Cloudinary module integration is enabled + And the Cloudinary module foldered mode is active + When image "apple.jpg" is added to product "Apple" + Then the image can be seen on the image provider in the correct folder for product "Apple" + + @e2e + Scenario: Administrator deletes product image + Given the product "Apple" exists + And the Cloudinary module credentials are set + And the Cloudinary module integration is enabled + And the Cloudinary module foldered mode is active + And the image provider has an image "apple.jpg" in the correct folder for product "Apple" + When I delete the images from product "Apple" + Then the image can not be seen on the image provider in the correct folder for product "Apple" diff --git a/magento1/features/e2e/upload.feature b/magento1/features/e2e/upload.feature new file mode 100644 index 0000000..8768bdf --- /dev/null +++ b/magento1/features/e2e/upload.feature @@ -0,0 +1,13 @@ +Feature: Uploading images to the image provider + In order to keep the image gallery content relevant + As a store admin + I want new product images to be uploaded to the image provider + + @e2e + Scenario: Administrator adds image to product + Given the product "Apple" exists + And the Cloudinary module credentials are set + And the Cloudinary module integration is enabled + And the Cloudinary module foldered mode is inactive + When image "apple.jpg" is added to product "Apple" + Then the image for product "Apple" can be seen in the image provider root folder diff --git a/magento1/features/image_provider_transform.feature b/magento1/features/image_provider_transform.feature new file mode 100755 index 0000000..8ec2938 --- /dev/null +++ b/magento1/features/image_provider_transform.feature @@ -0,0 +1,55 @@ +Feature: Getting transformed images from the provider + In order to reduce load times + As a Store Admin + I want provide transformed versions of the images + + Scenario: Getting a cropped image from the provider + Given there's an image "pink_dress.gif" in the image provider + When I ask the image provider for "pink_dress.gif" transformed to "100x150" + Then I should receive that image with the dimensions "100x150" + + @not-automated + Scenario: Getting a image without gravity transformation from the provider + Given my image provider has an image "pink_dress.gif" + When I ask the image provider for "pink_dress.gif" + Then I should receive that image with no gravity set + + @not-automated + Scenario: Getting a gravity transformed image from the provider + Given my image provider has an image "pink_dress.gif" + And I have set the default image gravity to "Center" + When I ask the image provider for "pink_dress.gif" + Then I should receive that image with gravity "Center" + + Scenario: Getting an optimised image from the image provider + Given there's an image "white_and_gold_dress.jpg" in the image provider + When I request the image from the image provider + Then I should get an optimised image from the image provider + + Scenario: Getting the original image from the image provider + Given there's an image "blue_and_black_dress.jpg" in the image provider + And image optimisation is disabled + When I request the image from the image provider + Then I should get the original image from the image provider + + Scenario: Getting an image at the default quality of 80 percent + Given there's an image "red-shirt.jpg" in the image provider + When I request the image from the image provider + Then I should get an image with 80 percent quality from the image provider + + Scenario: Changing image quality to 60 percent + Given there's an image "red-shirt.jpg" in the image provider + And I set image quality to 60 percent + When I request the image from the image provider + Then I should get an image with 60 percent quality from the image provider + + Scenario: Getting an image with the default DPR setting + Given there's an image "red-shirt.jpg" in the image provider + When I request the image from the image provider + Then I should get the image "red-shirt.jpg" with the default DPR + + Scenario: Changing the DRP setting + Given there's an image "red-shirt.jpg" in the image provider + And my DPR is set to 2.0 in the configuration + When I request the image from the image provider + Then I should get an image with DPR 2.0 \ No newline at end of file diff --git a/features/image_provider_upload.feature b/magento1/features/image_provider_upload.feature similarity index 100% rename from features/image_provider_upload.feature rename to magento1/features/image_provider_upload.feature diff --git a/magento1/features/migration/admin_migrates_images.feature b/magento1/features/migration/admin_migrates_images.feature new file mode 100755 index 0000000..4ca785a --- /dev/null +++ b/magento1/features/migration/admin_migrates_images.feature @@ -0,0 +1,31 @@ +@not-automated +Feature: Product image migration + In order to easily install and use the Cloudinary module + As an integrator + I need an easy mechanism to migrate all existing catalogue images to Cloudinary + + Scenario: Integrator triggers the migration + Given the media gallery contains the images "chair.png", "table.png" and "house.png" + And those images have not been migrated to cloudinary + When the integrator triggers the migration + Then the images should be migrated to cloudinary + + Scenario: Integrator is unable to start the migration when a process is already running + Given the cloudinary migration has been triggered + And the cloudinary migration is still in progress + When the integrator tries to trigger the migration + Then they should not be able to start the migration + And there should be feedback that triggering a migration is currently disabled + + Scenario: Integrator is unable to start the migration when there are no images to migrate + Given there are no images to migrate + When the integrator tries to trigger the migration + Then they should not be able to start the migration + And there should be feedback that triggering a migration is currently disabled + + Scenario: Integrator receives feedback of the migration progress + Given the media gallery contains the images "chair.png", "table.png" and "house.png" + And a migration has been started + When the images "chair.png" and "table.png" have been migrated + And the image "house.png" haven not been migrated yet + Then the integrator should receive feedback saying that the migration is at "66%" diff --git a/magento1/features/migration/cloudinary_enable_disable.feature b/magento1/features/migration/cloudinary_enable_disable.feature new file mode 100755 index 0000000..03d441c --- /dev/null +++ b/magento1/features/migration/cloudinary_enable_disable.feature @@ -0,0 +1,16 @@ +@javascript @ui +Feature: Cloudinary can be enabled or disabled + + Background: + Given I am logged in as an administrator + + Scenario: Being able to enable cloudinary when its disabled + Given the Cloudinary module is disabled + When I access the Cloudinary configuration + Then I should be able to enable the module + + Scenario: Being able to disable cloudinary when its enabled + Given the Cloudinary module is enabled + When I access the Cloudinary configuration + Then I should be able to disable the module + diff --git a/magento1/features/validate_credentials.feature b/magento1/features/validate_credentials.feature new file mode 100755 index 0000000..a5aab1a --- /dev/null +++ b/magento1/features/validate_credentials.feature @@ -0,0 +1,16 @@ +Feature: Validating environment variable used for image provider + In order to interact with the image provider + As a store admin + I want to know that the environment variable I have configured is valid + + @javascript @critical + Scenario: Validate correct environment variable is being used + Given I have used a valid environment variable in the configuration + When I ask the provider to validate my credentials + Then I should be informed my credentials are valid + + @javascript @critical + Scenario: Report an error if incorrect environment variable is used + Given I have used an invalid environment variable in the configuration + When I ask the provider to validate my credentials + Then I should be informed that my credentials are not valid \ No newline at end of file diff --git a/modman b/magento1/modman similarity index 100% rename from modman rename to magento1/modman diff --git a/phpspec.yml b/magento1/phpspec.yml similarity index 100% rename from phpspec.yml rename to magento1/phpspec.yml diff --git a/magento1/phpunit.xml.dist b/magento1/phpunit.xml.dist new file mode 100755 index 0000000..3bc5730 --- /dev/null +++ b/magento1/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + tests + + + + + + diff --git a/magento1/spec/Cloudinary/Cloudinary/Helper/CronSpec.php b/magento1/spec/Cloudinary/Cloudinary/Helper/CronSpec.php new file mode 100644 index 0000000..24b4f63 --- /dev/null +++ b/magento1/spec/Cloudinary/Cloudinary/Helper/CronSpec.php @@ -0,0 +1,79 @@ +shouldHaveType('Cloudinary_Cloudinary_Helper_Cron'); + } + + function it_validates_true_when_migration_is_not_running( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(false); + $this->validate($migration, 10)->shouldReturn(true); + } + + function it_validates_true_when_migration_is_running_and_time_elapsed_is_less_than_cron_interval( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(true); + $migration->timeElapsed()->willReturn(1); + $migration->hasProgress()->willReturn(false); + $this->validate($migration, 10)->shouldReturn(true); + } + + function it_validates_false_when_migration_is_running_and_time_elapsed_is_more_than_cron_interval_and_no_batches_have_been_processed( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(true); + $migration->timeElapsed()->willReturn(9999); + $migration->hasProgress()->willReturn(false); + $this->validate($migration, 10)->shouldReturn(false); + } + + function it_validates_true_when_migration_is_running_and_time_elapsed_is_more_than_cron_interval_and_batches_have_been_processed( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(true); + $migration->timeElapsed()->willReturn(9999); + $migration->hasProgress()->willReturn(true); + $this->validate($migration, 10)->shouldReturn(true); + } + + function it_confirms_migration_is_not_initialising_when_migration_has_not_started( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(false); + $migration->hasProgress()->willReturn(false); + $this->isInitialising($migration, 10)->shouldReturn(false); + } + + function it_confirms_migration_is_not_initialising_when_batches_have_been_processed( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(true); + $migration->hasProgress()->willReturn(true); + $this->isInitialising($migration, 10)->shouldReturn(false); + } + + function it_confirms_migration_initialising_when_migration_started_and_there_is_no_progress( + \Cloudinary_Cloudinary_Model_Migration $migration + ) + { + $migration->hasStarted()->willReturn(true); + $migration->hasProgress()->willReturn(false); + $this->isInitialising($migration, 10)->shouldReturn(true); + } +} diff --git a/spec/Cloudinary/Cloudinary/Model/MigrationSpec.php b/magento1/spec/Cloudinary/Cloudinary/Model/MigrationSpec.php similarity index 100% rename from spec/Cloudinary/Cloudinary/Model/MigrationSpec.php rename to magento1/spec/Cloudinary/Cloudinary/Model/MigrationSpec.php diff --git a/spec/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifierSpec.php b/magento1/spec/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifierSpec.php similarity index 100% rename from spec/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifierSpec.php rename to magento1/spec/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifierSpec.php diff --git a/magento1/spec/CloudinaryExtension/CloudSpec.php b/magento1/spec/CloudinaryExtension/CloudSpec.php new file mode 100755 index 0000000..341eb65 --- /dev/null +++ b/magento1/spec/CloudinaryExtension/CloudSpec.php @@ -0,0 +1,20 @@ +beConstructedThrough('fromName', ['cloud_name']); + } + + function it_is_initializable() + { + $this->shouldHaveType('CloudinaryExtension\Cloud'); + } +} diff --git a/spec/CloudinaryExtension/CloudinaryImageProviderSpec.php b/magento1/spec/CloudinaryExtension/CloudinaryImageProviderSpec.php similarity index 100% rename from spec/CloudinaryExtension/CloudinaryImageProviderSpec.php rename to magento1/spec/CloudinaryExtension/CloudinaryImageProviderSpec.php diff --git a/spec/CloudinaryExtension/ConfigurationSpec.php b/magento1/spec/CloudinaryExtension/ConfigurationSpec.php similarity index 100% rename from spec/CloudinaryExtension/ConfigurationSpec.php rename to magento1/spec/CloudinaryExtension/ConfigurationSpec.php diff --git a/spec/CloudinaryExtension/CredentialsSpec.php b/magento1/spec/CloudinaryExtension/CredentialsSpec.php similarity index 100% rename from spec/CloudinaryExtension/CredentialsSpec.php rename to magento1/spec/CloudinaryExtension/CredentialsSpec.php diff --git a/magento1/spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php b/magento1/spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php new file mode 100755 index 0000000..db9c1f7 --- /dev/null +++ b/magento1/spec/CloudinaryExtension/Image/Transformation/DimensionsSpec.php @@ -0,0 +1,40 @@ +beConstructedThrough('fromWidthAndHeight', [100, 150]); + } + + function it_exposes_its_width_and_height() + { + $this->getWidth()->shouldBe(100); + $this->getHeight()->shouldBe(150); + } + + function it_rounds_float_values() + { + $this->beConstructedThrough('fromWidthAndHeight', [1.5, 2.9]); + + $this->getWidth()->shouldBe(2); + $this->getHeight()->shouldBe(3); + } + + function it_exposes_null_properties_when_created_null() + { + $this->beConstructedThrough('null', []); + + $this->getWidth()->shouldBe(null); + $this->getHeight()->shouldBe(null); + } +} diff --git a/spec/CloudinaryExtension/Image/TransformationSpec.php b/magento1/spec/CloudinaryExtension/Image/TransformationSpec.php similarity index 100% rename from spec/CloudinaryExtension/Image/TransformationSpec.php rename to magento1/spec/CloudinaryExtension/Image/TransformationSpec.php diff --git a/magento1/spec/CloudinaryExtension/ImageSpec.php b/magento1/spec/CloudinaryExtension/ImageSpec.php new file mode 100755 index 0000000..35f0149 --- /dev/null +++ b/magento1/spec/CloudinaryExtension/ImageSpec.php @@ -0,0 +1,24 @@ +beConstructedThrough('fromPath', ['image_path.gif']); + } + + function it_provides_a_public_id_from_path() + { + $this->getId()->shouldBe('image_path'); + } + + function it_provides_the_extension_from_path() + { + $this->getExtension()->shouldBe('gif'); + } +} diff --git a/spec/CloudinaryExtension/Migration/BatchUploaderSpec.php b/magento1/spec/CloudinaryExtension/Migration/BatchUploaderSpec.php similarity index 100% rename from spec/CloudinaryExtension/Migration/BatchUploaderSpec.php rename to magento1/spec/CloudinaryExtension/Migration/BatchUploaderSpec.php diff --git a/magento1/spec/CloudinaryExtension/Migration/QueueSpec.php b/magento1/spec/CloudinaryExtension/Migration/QueueSpec.php new file mode 100755 index 0000000..0bbfbc8 --- /dev/null +++ b/magento1/spec/CloudinaryExtension/Migration/QueueSpec.php @@ -0,0 +1,71 @@ +beConstructedWith($migrationTask, $synchronizedMediaRepository, $batchUploader, $logger); + } + + function it_does_not_process_the_migration_queue_if_task_has_been_stopped( + Task $migrationTask, + SynchronizedMediaRepository $synchronizedMediaRepository, + Logger $logger + ) { + $migrationTask->hasBeenStopped()->willReturn(true); + + $synchronizedMediaRepository->findUnsynchronisedImages()->shouldNotBeCalled(); + $logger->notice(Argument::any())->shouldNotBeCalled(); + + $this->process(); + } + + + function it_processes_the_migration_queue_if_task_has_been_started( + Task $migrationTask, + SynchronizedMediaRepository $synchronizedMediaRepository, + Logger $logger, + BatchUploader $batchUploader + ) { + $migrationTask->hasBeenStopped()->willReturn(false); + $migrationTask->stop()->willReturn(); + + $logger->notice(Queue::MESSAGE_PROCESSING)->shouldBeCalled(); + $synchronizedMediaRepository->findUnsynchronisedImages()->willReturn(array('image1', 'image2')); + + $batchUploader->uploadImages(array('image1', 'image2'))->shouldBeCalled(); + + $this->process(); + } + + function it_stops_the_migration_task_if_there_is_nothing_left_to_process( + Task $migrationTask, + SynchronizedMediaRepository $synchronizedMediaRepository, + Logger $logger, + BatchUploader $batchUploader + ) { + $migrationTask->hasBeenStopped()->willReturn(false); + $synchronizedMediaRepository->findUnsynchronisedImages()->willReturn(array()); + + $logger->notice(Queue::MESSAGE_COMPLETE)->shouldBeCalled(); + $migrationTask->stop()->shouldBeCalled(); + + $batchUploader->uploadImages(Argument::any())->shouldNotBeCalled(); + + $this->process(); + } +} diff --git a/spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php b/magento1/spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php similarity index 100% rename from spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php rename to magento1/spec/CloudinaryExtension/Security/CloudinaryEnvironmentVariableSpec.php diff --git a/magento1/spec/CloudinaryExtension/Security/KeySpec.php b/magento1/spec/CloudinaryExtension/Security/KeySpec.php new file mode 100755 index 0000000..ba14236 --- /dev/null +++ b/magento1/spec/CloudinaryExtension/Security/KeySpec.php @@ -0,0 +1,19 @@ +beConstructedThrough('fromString', ['secret_key']); + } + + function it_is_initializable() + { + $this->shouldHaveType('CloudinaryExtension\Security\Key'); + } +} diff --git a/magento1/spec/CloudinaryExtension/Security/SecretSpec.php b/magento1/spec/CloudinaryExtension/Security/SecretSpec.php new file mode 100755 index 0000000..02641f9 --- /dev/null +++ b/magento1/spec/CloudinaryExtension/Security/SecretSpec.php @@ -0,0 +1,19 @@ +beConstructedThrough('fromString', ['secret_secret']); + } + + function it_is_initializable() + { + $this->shouldHaveType('CloudinaryExtension\Security\Secret'); + } +} diff --git a/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage/Grid.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage/Grid.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage/Grid.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Manage/Grid.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Page/Menu.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Page/Menu.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Page/Menu.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/Page/Menu.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/System/Config/Signup.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/System/Config/Signup.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/System/Config/Signup.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Block/Adminhtml/System/Config/Signup.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Helper/Autoloader.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Autoloader.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Helper/Autoloader.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Autoloader.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Helper/Console.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Console.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Helper/Console.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Console.php diff --git a/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Cron.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Cron.php new file mode 100644 index 0000000..83e63af --- /dev/null +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Cron.php @@ -0,0 +1,23 @@ +isInitialising($migration) && ($migration->timeElapsed() > $cronIntervalInSeconds)); + } + + /** + * @param Cloudinary_Cloudinary_Model_Migration $migration + * @return bool + */ + public function isInitialising(Cloudinary_Cloudinary_Model_Migration $migration) + { + return $migration->hasStarted() && !$migration->hasProgress(); + } +} diff --git a/src/app/code/community/Cloudinary/Cloudinary/Helper/Data.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Data.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Helper/Data.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Data.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Helper/Image.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Image.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Helper/Image.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Helper/Image.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Image.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Image.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Image.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Image.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media/Config.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media/Config.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media/Config.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Catalog/Product/Media/Config.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Adminhtml/Template/Filter.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Adminhtml/Template/Filter.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Adminhtml/Template/Filter.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Adminhtml/Template/Filter.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Synchronisation.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Synchronisation.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Synchronisation.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Synchronisation.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Template/Filter.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Template/Filter.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Template/Filter.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Template/Filter.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Uploader.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Uploader.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Uploader.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Uploader.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Wysiwyg/Images/Storage.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Wysiwyg/Images/Storage.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Wysiwyg/Images/Storage.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cms/Wysiwyg/Images/Storage.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/CollectionCounter.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/CollectionCounter.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/CollectionCounter.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/CollectionCounter.php diff --git a/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Configuration.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Configuration.php new file mode 100644 index 0000000..6326595 --- /dev/null +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Configuration.php @@ -0,0 +1,209 @@ +folderTranslator = Mage::getModel('cloudinary_cloudinary/magentoFolderTranslator'); + } + + /** + * @return Cloud + */ + public function getCloud() + { + return $this->getEnvironmentVariable()->getCloud(); + } + + /** + * @return Credentials + */ + public function getCredentials() + { + return $this->getEnvironmentVariable()->getCredentials(); + } + + /** + * @return Transformation + */ + public function getDefaultTransformation() + { + $transformation = Transformation::builder() + ->withGravity(Gravity::fromString($this->getDefaultGravity())) + ->withFetchFormat(FetchFormat::fromString($this->getFetchFormat())) + ->withQuality(Quality::fromString($this->getImageQuality())) + ->withDpr(Dpr::fromString($this->getImageDpr())); + + if ($this->isSmartServing()){ + $transformation + ->addFlags(['lossy']) + ->withFetchFormat(FetchFormat::fromString(FetchFormat::FETCH_FORMAT_AUTO)) + ->withoutFormat(); + } + return $transformation; + } + + /** + * @return boolean + */ + public function getCdnSubdomainStatus() + { + return Mage::getStoreConfig(self::CONFIG_CDN_SUBDOMAIN); + } + + /** + * @return string + */ + public function getUserPlatform() + { + return sprintf( + self::USER_PLATFORM_TEMPLATE, + Mage::getConfig()->getModuleConfig('Cloudinary_Cloudinary')->version, + Mage::getVersion() + ); + } + + /** + * @return UploadConfig + */ + public function getUploadConfig() + { + return UploadConfig::fromBooleanValues(true, false, false); + } + + /** + * @return boolean + */ + public function isEnabled() + { + return Mage::getStoreConfigFlag(self::CONFIG_PATH_ENABLED); + } + + public function enable() + { + $this->setStoreConfig(self::CONFIG_PATH_ENABLED, self::STATUS_ENABLED); + } + + public function disable() + { + $this->setStoreConfig(self::CONFIG_PATH_ENABLED, self::STATUS_DISABLED); + } + + public function getFormatsToPreserve() { + return ['png', 'webp', 'gif', 'svg']; + } + + public function validateCredentials() + { + try { + $api = new \Cloudinary\Api(); + return $api->ping((new ConfigurationBuilder($this))->build()); + } catch (Exception $e) { + Mage::logException($e); + } + return false; + } + + public function getMigratedPath($file) + { + if ($this->isFolderedMigration()) { + $result = $this->folderTranslator->translate($file); + } else { + $result = basename($file); + } + return $result; + } + + public function reverseMigratedPathIfNeeded($migratedPath) + { + if ($this->isFolderedMigration()) { + return $this->folderTranslator->reverse($migratedPath); + } + return $migratedPath; + } + + public function isFolderedMigration() + { + return Mage::getStoreConfigFlag(self::CONFIG_FOLDERED_MIGRATION); + } + + private function setStoreConfig($configPath, $value) + { + Mage::getModel('core/config')->saveConfig($configPath, $value)->reinit(); + } + + /** + * @return CloudinaryEnvironmentVariable + */ + private function getEnvironmentVariable() + { + if (is_null($this->environmentVariable)) { + $value = Mage::helper('core')->decrypt(Mage::getStoreConfig(self::CONFIG_PATH_ENVIRONMENT_VARIABLE)); + $this->environmentVariable = CloudinaryEnvironmentVariable::fromString($value); + } + return $this->environmentVariable; + } + + /** + * Smart serving means lossy compression and automatic fetch format. + * @return bool + */ + private function isSmartServing() + { + return Mage::getStoreConfigFlag(self::CONFIG_SMART_SERVING); + } + + private function getDefaultGravity() + { + return Mage::getStoreConfig(self::CONFIG_DEFAULT_GRAVITY); + } + + /** + * @return null|string + */ + private function getFetchFormat() + { + if (Mage::getStoreConfigFlag(self::CONFIG_DEFAULT_FETCH_FORMAT)) { + return FetchFormat::FETCH_FORMAT_AUTO; + } + return ''; + } + + private function getImageQuality() + { + return Mage::getStoreConfig(self::CONFIG_DEFAULT_QUALITY); + } + + private function getImageDpr() + { + return Mage::getStoreConfig(self::CONFIG_DEFAULT_DPR); + } +} diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Cron.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cron.php similarity index 83% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Cron.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cron.php index 6bd4682..51e4aac 100755 --- a/src/app/code/community/Cloudinary/Cloudinary/Model/Cron.php +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Cron.php @@ -1,6 +1,7 @@ process(); - foreach ($batchUploader->getMigrationErrors() as $error) { - Cloudinary_Cloudinary_Model_MigrationError::saveFromException($error); - } - return $this; } } diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Exception/BadFilePathException.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Exception/BadFilePathException.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Exception/BadFilePathException.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Exception/BadFilePathException.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Image.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Image.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Image.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Image.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Logger.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Logger.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Logger.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Logger.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/MagentoFolderTranslator.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/MagentoFolderTranslator.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/MagentoFolderTranslator.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/MagentoFolderTranslator.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Migration.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Migration.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Migration.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Migration.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php similarity index 50% rename from src/app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php index 721533c..ae48b8a 100644 --- a/src/app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/MigrationError.php @@ -3,11 +3,16 @@ class Cloudinary_Cloudinary_Model_MigrationError extends Mage_Core_Model_Abstract { + const REMOVE_ORPHAN_MESSAGE = 'Image found in sync table that no longer exists. Removing reference: %s'; + public function __construct() { $this->_init('cloudinary_cloudinary/migrationError'); } + /** + * @param \CloudinaryExtension\Exception\MigrationError $e + */ public static function saveFromException(\CloudinaryExtension\Exception\MigrationError $e) { $image = $e->getImage(); @@ -23,4 +28,17 @@ public static function saveFromException(\CloudinaryExtension\Exception\Migratio $entry->save(); } + + /** + * @param Cloudinary_Cloudinary_Model_Synchronisation $orphanImage + * @return $this + */ + public function orphanRemoved(Cloudinary_Cloudinary_Model_Synchronisation $orphanImage) + { + $this->setFilePath($orphanImage->getImageName()); + $this->setRelativePath($orphanImage->getImageName()); + $this->setMessage(sprintf(self::REMOVE_ORPHAN_MESSAGE, $orphanImage->getImageName())); + $this->setTimestamp(time()); + return $this; + } } diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Observer.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Observer.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Observer.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Observer.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php similarity index 63% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php index fed4da7..fcb3967 100755 --- a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Cms/Synchronisation/Collection.php @@ -1,5 +1,6 @@ addFieldToFilter('media_gallery_id', array('null' => true)) ->getData(); } + + private function _getSynchronisedRawImageNames() + { + $result = array_map( + function ($itemData) { + return $itemData['image_name']; + }, + $this->_getSynchronisedImageData() + ); + + return $result; + } + + /** + * @return [Cloudinary_Cloudinary_Model_Synchronisation] + */ + public function findOrphanedSynchronisedImages() + { + return $this->_synchronisationCollectionFromImageNames( + array_diff( + $this->_getSynchronisedRawImageNames(), + $this->_extractRelativePaths($this->getItems()) + ) + ); + } + + /** + * @param [string] $imageNames + * @return [Cloudinary_Cloudinary_Model_Synchronisation] + */ + private function _synchronisationCollectionFromImageNames(array $imageNames) + { + return Mage::getModel('cloudinary_cloudinary/synchronisation') + ->getCollection() + ->addFieldToFilter('image_name', ['in' => $imageNames]) + ->getItems(); + } + + /** + * @param [Synchronizable] $items + * @return [string] + */ + private function _extractRelativePaths(array $items) + { + return array_map( + function(Synchronizable $syncItem) { + return $syncItem->getRelativePath(); + }, + $items + ); + } } diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Media/Collection.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Media/Collection.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Media/Collection.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Media/Collection.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Migration.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Migration.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Migration.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Migration.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError/Collection.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError/Collection.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError/Collection.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/MigrationError/Collection.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php similarity index 89% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php index 5520440..496afdc 100755 --- a/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Resource/Synchronisation/Collection.php @@ -60,4 +60,13 @@ private function getQueryForSyncedImageNames() $select->where('media_gallery_value is not null'); return $select->columns('media_gallery_value'); } + + /** + * Only applicable for cms instance + * @return [Cloudinary_Cloudinary_Model_Synchronisation] + */ + public function findOrphanedSynchronisedImages() + { + return []; + } } diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/Synchronisation.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Synchronisation.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/Synchronisation.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/Synchronisation.php diff --git a/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php new file mode 100755 index 0000000..5a105af --- /dev/null +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php @@ -0,0 +1,57 @@ +_synchronisedMediaRepositories = $synchronisedMediaRepositories; + } + + /** + * @param int $limit + * @return [Cloudinary_Cloudinary_Model_Synchronisation] + */ + public function findUnsynchronisedImages($limit = 200) + { + return array_slice($this->findUnlimitedUnsynchronisedImages(), 0, $limit); + } + + /** + * @return [Cloudinary_Cloudinary_Model_Synchronisation] + */ + private function findUnlimitedUnsynchronisedImages() + { + return array_reduce( + $this->_synchronisedMediaRepositories, + function($carry, $synchronisedMediaRepository) { + return $carry + $synchronisedMediaRepository->findUnsynchronisedImages(); + }, + [] + ); + } + + /** + * @return [Cloudinary_Cloudinary_Model_Synchronisation] + */ + public function findOrphanedSynchronisedImages() + { + return array_reduce( + $this->_synchronisedMediaRepositories, + function($carry, $synchronisedMediaRepository) { + return $carry + $synchronisedMediaRepository->findOrphanedSynchronisedImages(); + }, + [] + ); + } +} diff --git a/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronizationChecker.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronizationChecker.php new file mode 100644 index 0000000..c46eaf5 --- /dev/null +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronizationChecker.php @@ -0,0 +1,18 @@ +getCollection(); + $table = $coll->getMainTable(); + // case sensitive check + $query = "select count(*) from $table where binary image_name = '$imageName' limit 1"; + return $coll->getConnection()->query($query)->fetchColumn() > 0; + } +} diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Dpr.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Dpr.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Dpr.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Dpr.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Gravity.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Gravity.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Gravity.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Gravity.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Quality.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Quality.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Quality.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/Model/System/Config/Source/Dropdown/Quality.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php similarity index 84% rename from src/app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php index 1814f6c..f7ec55a 100755 --- a/src/app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/controllers/Adminhtml/CloudinaryController.php @@ -4,6 +4,9 @@ class Cloudinary_Cloudinary_Adminhtml_CloudinaryController extends Mage_Adminhtm { const CRON_INTERVAL = 300; + /** + * @var Cloudinary_Cloudinary_Model_Migration + */ private $_migrationTask; /** @@ -21,6 +24,8 @@ public function preDispatch() public function indexAction() { + $this->removeOrphanSyncEntries(); + $this->_displayMigrationMessages(); $layout = $this->loadLayout(); @@ -43,6 +48,22 @@ public function indexAction() $this->renderLayout(); } + public function removeOrphanSyncEntries() + { + $combinedMediaRepository = Mage::getModel( + 'cloudinary_cloudinary/synchronisedMediaUnifier', + [ + Mage::getResourceModel('cloudinary_cloudinary/synchronisation_collection'), + Mage::getResourceModel('cloudinary_cloudinary/cms_synchronisation_collection') + ] + ); + + foreach ($combinedMediaRepository->findOrphanedSynchronisedImages() as $orphanImage) { + Mage::getModel('cloudinary_cloudinary/migrationError')->orphanRemoved($orphanImage)->save(); + $orphanImage->delete(); + } + } + public function configAction() { $this->_redirect("*/system_config/edit/section/cloudinary/"); diff --git a/src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-0.1.0-0.1.1.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-0.1.0-0.1.1.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-0.1.0-0.1.1.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-0.1.0-0.1.1.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-1.1.2-1.1.3.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-1.1.2-1.1.3.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-1.1.2-1.1.3.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/data/cloudinary_setup/data-upgrade-1.1.2-1.1.3.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/etc/adminhtml.xml b/magento1/src/app/code/community/Cloudinary/Cloudinary/etc/adminhtml.xml similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/etc/adminhtml.xml rename to magento1/src/app/code/community/Cloudinary/Cloudinary/etc/adminhtml.xml diff --git a/src/app/code/community/Cloudinary/Cloudinary/etc/config.xml b/magento1/src/app/code/community/Cloudinary/Cloudinary/etc/config.xml similarity index 99% rename from src/app/code/community/Cloudinary/Cloudinary/etc/config.xml rename to magento1/src/app/code/community/Cloudinary/Cloudinary/etc/config.xml index b4929aa..abf73ca 100755 --- a/src/app/code/community/Cloudinary/Cloudinary/etc/config.xml +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/etc/config.xml @@ -2,7 +2,7 @@ - 2.1.0 + 2.2.0 diff --git a/src/app/code/community/Cloudinary/Cloudinary/etc/system.xml b/magento1/src/app/code/community/Cloudinary/Cloudinary/etc/system.xml similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/etc/system.xml rename to magento1/src/app/code/community/Cloudinary/Cloudinary/etc/system.xml diff --git a/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/install-0.1.0.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/install-0.1.0.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/install-0.1.0.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/install-0.1.0.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-0.1.0-0.1.1.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-0.1.0-0.1.1.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-0.1.0-0.1.1.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-0.1.0-0.1.1.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.3-1.1.4.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.3-1.1.4.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.3-1.1.4.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.3-1.1.4.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.4-1.1.5.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.4-1.1.5.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.4-1.1.5.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.4-1.1.5.php diff --git a/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.5-1.1.6.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.5-1.1.6.php similarity index 100% rename from src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.5-1.1.6.php rename to magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-1.1.5-1.1.6.php diff --git a/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-2.0.0-2.1.0.php b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-2.0.0-2.1.0.php new file mode 100644 index 0000000..18c2d6b --- /dev/null +++ b/magento1/src/app/code/community/Cloudinary/Cloudinary/sql/cloudinary_setup/upgrade-2.0.0-2.1.0.php @@ -0,0 +1,33 @@ +startSetup(); + +$conn = $installer->getConnection(); +$table = $installer->getTable('cloudinary_cloudinary/migration'); + +$conn->addColumn( + $table, + 'started_at', + [ + 'type' => Varien_Db_Ddl_Table::TYPE_DATETIME, + 'comment' => 'The time the migration started', + 'nullable' => true, + 'default' => '0000-00-00 00:00:00' + ] +); + +$conn->addColumn( + $table, + 'batch_count', + [ + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'comment' => 'Batches run for current migration', + 'nullable' => false, + 'default' => 0 + ] +); + +$installer->endSetup(); diff --git a/src/app/design/adminhtml/default/default/layout/cloudinary/cloudinary.xml b/magento1/src/app/design/adminhtml/default/default/layout/cloudinary/cloudinary.xml similarity index 100% rename from src/app/design/adminhtml/default/default/layout/cloudinary/cloudinary.xml rename to magento1/src/app/design/adminhtml/default/default/layout/cloudinary/cloudinary.xml diff --git a/src/app/design/adminhtml/default/default/template/cloudinary/manage.phtml b/magento1/src/app/design/adminhtml/default/default/template/cloudinary/manage.phtml similarity index 100% rename from src/app/design/adminhtml/default/default/template/cloudinary/manage.phtml rename to magento1/src/app/design/adminhtml/default/default/template/cloudinary/manage.phtml diff --git a/src/app/design/adminhtml/default/default/template/cloudinary/system/config/signup.phtml b/magento1/src/app/design/adminhtml/default/default/template/cloudinary/system/config/signup.phtml similarity index 100% rename from src/app/design/adminhtml/default/default/template/cloudinary/system/config/signup.phtml rename to magento1/src/app/design/adminhtml/default/default/template/cloudinary/system/config/signup.phtml diff --git a/src/app/etc/modules/Cloudinary_Cloudinary.xml b/magento1/src/app/etc/modules/Cloudinary_Cloudinary.xml similarity index 100% rename from src/app/etc/modules/Cloudinary_Cloudinary.xml rename to magento1/src/app/etc/modules/Cloudinary_Cloudinary.xml diff --git a/magento1/src/lib/CloudinaryExtension/Cloud.php b/magento1/src/lib/CloudinaryExtension/Cloud.php new file mode 100755 index 0000000..559a02c --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Cloud.php @@ -0,0 +1,25 @@ +cloudName = (string)$cloudName; + } + + public static function fromName($aCloudName) + { + return new Cloud($aCloudName); + } + + public function __toString() + { + return $this->cloudName; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/CloudinaryImageProvider.php b/magento1/src/lib/CloudinaryExtension/CloudinaryImageProvider.php new file mode 100755 index 0000000..ecb9385 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/CloudinaryImageProvider.php @@ -0,0 +1,89 @@ + true, + "unique_filename" => false, + "overwrite" => false + ); + + private function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + $this->authorise(); + } + + public static function fromConfiguration(Configuration $configuration) + { + return new CloudinaryImageProvider($configuration); + } + + public function upload(Image $image) + { + try{ + $imagePath = (string)$image; + $uploadOptionsAndFolder = $this->uploadConfig + ["folder" => $image->getRelativeFolder()]; + $uploadResult = Uploader::upload($imagePath, $uploadOptionsAndFolder); + + if ($uploadResult['existing'] == 1) { + MigrationError::throwWith($image, MigrationError::CODE_FILE_ALREADY_EXISTS); + } + return $uploadResult; + } catch (\Exception $e) { + MigrationError::throwWith($image, MigrationError::CODE_API_ERROR, $e->getMessage()); + } + } + + public function transformImage(Image $image, Transformation $transformation = null) + { + if ($transformation === null) { + $transformation = $this->configuration->getDefaultTransformation(); + } + return Image::fromPath(\cloudinary_url($image->getId(), $transformation->build()), $image->getRelativePath()); + } + + public function validateCredentials() + { + $signedValidationUrl = $this->getSignedValidationUrl(); + return $this->validationResult($signedValidationUrl); + } + + public function deleteImage(Image $image) + { + Uploader::destroy($image->getId()); + } + + private function authorise() + { + Cloudinary::config($this->configuration->build()); + Cloudinary::$USER_PLATFORM = $this->configuration->getUserPlatform(); + } + + private function getSignedValidationUrl() + { + $consoleUrl = Security\ConsoleUrl::fromPath("media_library/cms"); + return (string)Security\SignedConsoleUrl::fromConsoleUrlAndCredentials( + $consoleUrl, + $this->configuration->getCredentials() + ); + } + + private function validationResult($signedValidationUrl) + { + $request = new ValidateRemoteUrlRequest($signedValidationUrl); + return $request->validate(); + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Configuration.php b/magento1/src/lib/CloudinaryExtension/Configuration.php new file mode 100755 index 0000000..8b57648 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Configuration.php @@ -0,0 +1,97 @@ +cdnSubdomain = false; + $this->credentials = $credentials; + $this->cloud = $cloud; + $this->defaultTransformation = Transformation::builder(); + } + + public static function fromCloudAndCredentials(Cloud $cloud, Credentials $credentials) + { + return new Configuration($cloud, $credentials); + } + + public static function fromEnvironmentVariable(EnvironmentVariable $environmentVariable) + { + return new Configuration($environmentVariable->getCloud(), $environmentVariable->getCredentials()); + } + + public function getCloud() + { + return $this->cloud; + } + + public function getCredentials() + { + return $this->credentials; + } + + public function getDefaultTransformation() + { + return $this->defaultTransformation; + } + + public function build() + { + $configuration = $this->getMandatoryConfiguration(); + if($this->cdnSubdomain) { + $configuration['cdn_subdomain'] = true; + } + + return $configuration; + } + + public function enableCdnSubdomain() + { + $this->cdnSubdomain = true; + } + + public function getCdnSubdomainStatus() + { + return $this->cdnSubdomain; + } + + private function getMandatoryConfiguration() + { + return array( + "cloud_name" => (string)$this->cloud, + "api_key" => (string)$this->credentials->getKey(), + "api_secret" => (string)$this->credentials->getSecret() + ); + } + + /** + * @return string + */ + public function getUserPlatform() + { + return $this->userPlatform; + } + + /** + * @param string $userPlatform + */ + public function setUserPlatform($userPlatform) + { + $this->userPlatform = $userPlatform; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Credentials.php b/magento1/src/lib/CloudinaryExtension/Credentials.php new file mode 100755 index 0000000..c49c5e2 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Credentials.php @@ -0,0 +1,30 @@ +key = $key; + $this->secret = $secret; + } + + public function getKey() + { + return $this->key; + } + + public function getSecret() + { + return $this->secret; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Exception/InvalidCredentials.php b/magento1/src/lib/CloudinaryExtension/Exception/InvalidCredentials.php new file mode 100755 index 0000000..5dd83b8 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Exception/InvalidCredentials.php @@ -0,0 +1,10 @@ + 'File already exists (cloudinary is case insensitive!!).', + self::CODE_API_ERROR => 'Internal API error' + ]; + + private $image; + + /** + * @return Image + */ + public function getImage() + { + return $this->image; + } + + /** + * @param Image $image + * @param $code + * @param $message overrides the default message attached to the code + * @return MigrationError + */ + private static function build(Image $image, $code, $message = '') + { + $result = new MigrationError($message ?: self::$messages[$code], $code); + $result->image = $image; + return $result; + } + + public static function throwWith(Image $image, $code, $message = '') + { + throw self::build($image, $code, $message); + } + +} diff --git a/magento1/src/lib/CloudinaryExtension/FolderTranslator.php b/magento1/src/lib/CloudinaryExtension/FolderTranslator.php new file mode 100644 index 0000000..2949dc6 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/FolderTranslator.php @@ -0,0 +1,16 @@ +imagePath = $imagePath; + $this->relativePath = $relativePath; + $this->pathInfo = pathinfo($this->imagePath); + } + + public static function fromPath($imagePath, $relativePath = '') + { + return new Image($imagePath, $relativePath); + } + + public function __toString() + { + return $this->imagePath; + } + + public function getRelativePath() + { + return $this->relativePath; + } + + public function getRelativeFolder() + { + $result = dirname($this->getRelativePath()); + return $result == '.' ? '' : $result; + } + + public function getId() + { + if ($this->relativePath) { + return $this->getRelativeFolder() . DS . $this->pathInfo['filename']; + } else { + return $this->pathInfo['filename']; + } + } + + public function getExtension() + { + return $this->pathInfo['extension']; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Image/Synchronizable.php b/magento1/src/lib/CloudinaryExtension/Image/Synchronizable.php new file mode 100755 index 0000000..c6a4aed --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Synchronizable.php @@ -0,0 +1,10 @@ +fetchFormat = FetchFormat::fromString(Format::FETCH_FORMAT_AUTO); + $this->crop = 'pad'; + $this->format = Format::fromExtension('jpg'); + $this->validFormats = array('gif', 'jpg', 'png', 'svg'); + } + + public function withGravity(Gravity $gravity) + { + $this->gravity = $gravity; + $this->crop = ((string) $gravity) ? 'crop' : 'pad'; + + return $this; + } + + public function withDimensions(Dimensions $dimensions) + { + $this->dimensions = $dimensions; + + return $this; + } + + public function withFetchFormat(FetchFormat $fetchFormat) + { + $this->fetchFormat = $fetchFormat; + + return $this; + } + + public function withFormat(Format $format) + { + if (in_array((string) $format, $this->validFormats)) { + $this->format = $format; + } + + return $this; + } + + public function withQuality(Quality $quality) + { + $this->quality = $quality; + + return $this; + } + + public function withDpr(Dpr $dpr) + { + $this->dpr = $dpr; + + return $this; + } + + public function withOptimisationDisabled() + { + $this->withFetchFormat(FetchFormat::fromString('')); + return $this; + } + + public static function builder() + { + return new Transformation(); + } + + public function build() + { + return array( + 'fetch_format' => (string) $this->fetchFormat, + 'quality' => (string) $this->quality, + 'crop' => (string) $this->crop, + 'gravity' => (string) $this->gravity ?: null, + 'width' => $this->dimensions ? $this->dimensions->getWidth() : null, + 'height' => $this->dimensions ? $this->dimensions->getHeight() : null, + 'format' => (string) $this->format, + 'dpr' => (string) $this->dpr + ); + } +} + diff --git a/magento1/src/lib/CloudinaryExtension/Image/Transformation/Dimensions.php b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Dimensions.php new file mode 100755 index 0000000..ec0fa8b --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Dimensions.php @@ -0,0 +1,36 @@ +width = is_null($width) ? null : (int) round($width); + $this->height = is_null($height) ? null : (int) round($height); + } + + public function getWidth() + { + return $this->width; + } + + public function getHeight() + { + return $this->height; + } + + public static function fromWidthAndHeight($width, $height) + { + return new Dimensions($width, $height); + } + + public static function null() + { + return new Dimensions(null, null); + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Image/Transformation/Dpr.php b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Dpr.php new file mode 100755 index 0000000..8c15aa5 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Dpr.php @@ -0,0 +1,23 @@ +value = $value; + } + + public static function fromString($value) + { + return new Dpr($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Image/Transformation/FetchFormat.php b/magento1/src/lib/CloudinaryExtension/Image/Transformation/FetchFormat.php new file mode 100755 index 0000000..8b34308 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Transformation/FetchFormat.php @@ -0,0 +1,25 @@ +value = $value; + } + + public static function fromString($value) + { + return new FetchFormat($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Image/Transformation/Format.php b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Format.php new file mode 100755 index 0000000..94da009 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Format.php @@ -0,0 +1,25 @@ +value = $value; + } + + public static function fromExtension($value) + { + return new Format($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Image/Transformation/Gravity.php b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Gravity.php new file mode 100755 index 0000000..9eb0d1a --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Gravity.php @@ -0,0 +1,30 @@ +value = $value; + } + + public function __toString() + { + return $this->value; + } + + public static function fromString($value) + { + return new Gravity($value); + } + + public static function null() + { + return new Gravity(null); + } +} + + diff --git a/magento1/src/lib/CloudinaryExtension/Image/Transformation/Quality.php b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Quality.php new file mode 100755 index 0000000..6b7f4d3 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Image/Transformation/Quality.php @@ -0,0 +1,23 @@ +value = $value; + } + + public static function fromString($value) + { + return new Quality($value); + } + + public function __toString() + { + return $this->value; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/ImageProvider.php b/magento1/src/lib/CloudinaryExtension/ImageProvider.php new file mode 100755 index 0000000..ea140aa --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/ImageProvider.php @@ -0,0 +1,13 @@ +imageProvider = $imageProvider; + $this->migrationTask = $migrationTask; + $this->baseMediaPath = $baseMediaPath; + $this->logger = $logger; + } + + public function uploadImages(array $images) + { + $this->countMigrated = 0; + foreach ($images as $image) { + + if ($this->migrationTask->hasBeenStopped()) { + break; + } + $this->uploadImage($image); + } + $this->logger->notice(sprintf(self::MESSAGE_STATUS, $this->countMigrated, $this->countFailed)); + } + + private function getAbsolutePath(Synchronizable $image) + { + return sprintf('%s%s', $this->baseMediaPath, $image->getFilename()); + } + + private function uploadImage(Synchronizable $image) + { + $absolutePath = $this->getAbsolutePath($image); + $relativePath = $image->getRelativePath(); + $apiImage = Image::fromPath($absolutePath, $relativePath); + + try { + $this->imageProvider->upload($apiImage); + $image->tagAsSynchronized(); + $this->countMigrated++; + $this->logger->notice(sprintf(self::MESSAGE_UPLOADED, $absolutePath . ' - ' . $relativePath)); + } catch (\Exception $e) { + $this->errors[] = $e; + $this->countFailed++; + $this->logger->error(sprintf(self::MESSAGE_UPLOAD_ERROR, $e->getMessage(), $absolutePath . ' - ' . $relativePath)); + } + } + + /** + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + public function getMigrationErrors() + { + return array_filter($this->errors, function ($val) { + return $val instanceof MigrationError; + }); + } + +} diff --git a/magento1/src/lib/CloudinaryExtension/Migration/Logger.php b/magento1/src/lib/CloudinaryExtension/Migration/Logger.php new file mode 100755 index 0000000..74bba5a --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Migration/Logger.php @@ -0,0 +1,14 @@ +migrationTask = $migrationTask; + $this->synchronizedMediaRepository = $synchronizedMediaRepository; + $this->logger = $logger; + $this->batchUploader = $batchUploader; + } + + public function process() + { + if ($this->migrationTask->hasBeenStopped()) { + return; + } + + $images = $this->synchronizedMediaRepository->findUnsynchronisedImages(); + + if (!$images) { + $this->logger->notice(self::MESSAGE_COMPLETE); + $this->migrationTask->stop(); + } else { + $this->logger->notice(self::MESSAGE_PROCESSING); + $this->batchUploader->uploadImages($images); + } + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php b/magento1/src/lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php new file mode 100755 index 0000000..c01dc34 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Migration/SynchronizedMediaRepository.php @@ -0,0 +1,8 @@ +apiSignature = Cloudinary::api_sign_request($params, (string) $secret); + } + + public static function fromSecretAndParams(Secret $secret, array $params = array()) + { + return new ApiSignature($secret, $params); + } + + public function __toString() + { + return $this->apiSignature; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Security/CloudinaryEnvironmentVariable.php b/magento1/src/lib/CloudinaryExtension/Security/CloudinaryEnvironmentVariable.php new file mode 100755 index 0000000..1dcc5d0 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Security/CloudinaryEnvironmentVariable.php @@ -0,0 +1,50 @@ +environmentVariable = (string)$environmentVariable; + $cloudinaryUrl = str_replace('CLOUDINARY_URL=', '', $environmentVariable); + if ($this->isUrlValid($cloudinaryUrl)) { + Cloudinary::config_from_url($cloudinaryUrl); + } + + } + + public static function fromString($environmentVariable) + { + return new CloudinaryEnvironmentVariable($environmentVariable); + } + + public function getCloud() + { + return Cloud::fromName(Cloudinary::config_get('cloud_name')); + } + + public function getCredentials() + { + return new Credentials( + Key::fromString(Cloudinary::config_get('api_key')), + Secret::fromString(Cloudinary::config_get('api_secret')) + ); + } + + public function __toString() + { + return $this->environmentVariable; + } + + private function isUrlValid($cloudinaryUrl) + { + return parse_url($cloudinaryUrl, PHP_URL_SCHEME) == "cloudinary"; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Security/ConsoleUrl.php b/magento1/src/lib/CloudinaryExtension/Security/ConsoleUrl.php new file mode 100755 index 0000000..e34c900 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Security/ConsoleUrl.php @@ -0,0 +1,26 @@ +consoleUrl = self::CLOUDINARY_CONSOLE_BASE_URL . $path; + } + + public static function fromPath($path) + { + return new ConsoleUrl($path); + } + + public function __toString() + { + return $this->consoleUrl; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Security/EnvironmentVariable.php b/magento1/src/lib/CloudinaryExtension/Security/EnvironmentVariable.php new file mode 100755 index 0000000..ae51017 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Security/EnvironmentVariable.php @@ -0,0 +1,9 @@ +key = (string)$key; + } + + public static function fromString($aKey) + { + return new Key($aKey); + } + + public function __toString() + { + return $this->key; + } + +} diff --git a/magento1/src/lib/CloudinaryExtension/Security/Secret.php b/magento1/src/lib/CloudinaryExtension/Security/Secret.php new file mode 100755 index 0000000..5a57ef8 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Security/Secret.php @@ -0,0 +1,24 @@ +secret = (string)$secret; + } + + public static function fromString($aSecret) + { + return new Secret($aSecret); + } + + public function __toString() + { + return $this->secret; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/Security/SignedConsoleUrl.php b/magento1/src/lib/CloudinaryExtension/Security/SignedConsoleUrl.php new file mode 100755 index 0000000..dce9b23 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/Security/SignedConsoleUrl.php @@ -0,0 +1,31 @@ + time(), "mode" => "check"); + $params["signature"] = (string)ApiSignature::fromSecretAndParams($credentials->getSecret(), $params); + $params["api_key"] = (string)$credentials->getKey(); + $query = http_build_query($params); + + $this->signedConsoleUrl = (string)$url . '?' . $query; + } + + public static function fromConsoleUrlAndCredentials(ConsoleUrl $url, Credentials $credentials) + { + return new SignedConsoleUrl($url, $credentials); + } + + public function __toString() + { + return $this->signedConsoleUrl; + } +} diff --git a/magento1/src/lib/CloudinaryExtension/ValidateRemoteUrlRequest.php b/magento1/src/lib/CloudinaryExtension/ValidateRemoteUrlRequest.php new file mode 100755 index 0000000..b2c5499 --- /dev/null +++ b/magento1/src/lib/CloudinaryExtension/ValidateRemoteUrlRequest.php @@ -0,0 +1,55 @@ +curlHandler = curl_init($url); + $this->setCurlOptions(); + } + + public function validate() + { + $result = $this->execute(); + + if ($result->responseCode == 200 && is_null($result->error)) { + return true; + } + return false; + } + + private function execute() + { + curl_exec($this->curlHandler); + + $result = new \stdClass(); + $result->responseCode = $this->getResponseCode(); + $result->error = $this->getErrorMessage(); + + curl_close($this->curlHandler); + + return $result; + } + + private function getResponseCode() + { + return curl_getinfo($this->curlHandler, CURLINFO_HTTP_CODE); + } + + private function getErrorMessage() + { + return curl_errno($this->curlHandler) ? curl_error($this->curlHandler) : null; + } + + private function setCurlOptions() + { + curl_setopt($this->curlHandler, CURLOPT_HEADER, 1); + curl_setopt($this->curlHandler, CURLOPT_FAILONERROR, 1); + curl_setopt($this->curlHandler, CURLOPT_RETURNTRANSFER, 1); + } +} diff --git a/src/var/connect/Cloudinary_Cloudinary.xml b/magento1/src/var/connect/Cloudinary_Cloudinary.xml similarity index 88% rename from src/var/connect/Cloudinary_Cloudinary.xml rename to magento1/src/var/connect/Cloudinary_Cloudinary.xml index efdde5f..572683d 100644 --- a/src/var/connect/Cloudinary_Cloudinary.xml +++ b/magento1/src/var/connect/Cloudinary_Cloudinary.xml @@ -9,13 +9,15 @@ Cloudinary supercharges your images! Upload images to the cloud, deliver optimized via a fast CDN, perform smart resizing and apply effects. MIT License (MITL) - 2.1.0 + 2.2.0 stable - v1.2.0 - - Release Highlights: - - * Foldered migration + + Release 2.2.0 notes: + - Existing images are not remigrated during migration, and do not fail migration + - Fix bug where migration status shows value above 100% + - Preserve original file extensions in Cloudinary image urls + - Retry migration when network failures occur + Cloudinary diff --git a/magento2/.gitignore b/magento2/.gitignore new file mode 100644 index 0000000..5849395 --- /dev/null +++ b/magento2/.gitignore @@ -0,0 +1,6 @@ +bin/ +vendor/ +composer.phar +src/lib/Cloudinary/.gitignore +.cp-remote-env-settings.yml +cp-remote-logs/ diff --git a/magento2/Api/SynchronisationRepositoryInterface.php b/magento2/Api/SynchronisationRepositoryInterface.php new file mode 100644 index 0000000..8446a1b --- /dev/null +++ b/magento2/Api/SynchronisationRepositoryInterface.php @@ -0,0 +1,22 @@ +batchUploader = $batchUploader; + } + + /** + * Configure the command + * + * @return void + */ + protected function configure() + { + $this->setName('cloudinary:upload:all'); + $this->setDescription('Upload unsynchronised images'); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $this->batchUploader->uploadUnsynchronisedImages($output); + } catch (\Exception $e) { + $output->writeln($e->getMessage()); + } + } +} diff --git a/magento2/Model/BatchUploader.php b/magento2/Model/BatchUploader.php new file mode 100644 index 0000000..1fda783 --- /dev/null +++ b/magento2/Model/BatchUploader.php @@ -0,0 +1,99 @@ +imageRepository = $imageRepository; + $this->configuration = $configuration; + $this->migrationTask = $migrationTask; + $this->cloudinaryImageManager = $cloudinaryImageManager; + } + + /** + * Find unsynchronised images and upload them to cloudinary + * + * @param OutputInterface|null $output + * @return bool + * @throws \Exception + */ + public function uploadUnsynchronisedImages(OutputInterface $output = null) + { + if ($this->migrationTask->hasStarted()) { + $this->displayMessage($output, self::ERROR_MIGRATION_ALREADY_RUNNING); + return false; + } + + try { + $this->migrationTask->start(); + + $images = $this->imageRepository->findUnsynchronisedImages(); + foreach ($images as $image) { + $this->displayMessage($output, sprintf(self::MESSAGE_UPLOAD_IMAGE, $image)); + $this->cloudinaryImageManager->uploadAndSynchronise($image); + } + + $this->migrationTask->stop(); + $this->displayMessage($output, sprintf(self::MESSAGE_UPLOAD_COMPLETE, count($images))); + + return true; + + } catch (\Exception $e) { + $this->migrationTask->stop(); + throw $e; + } + } + + /** + * @param OutputInterface $output + * @param string $message + */ + private function displayMessage(OutputInterface $output, $message) + { + if ($output) { + $output->writeln($message); + } + } +} diff --git a/magento2/Model/Config/Source/Dropdown/Dpr.php b/magento2/Model/Config/Source/Dropdown/Dpr.php new file mode 100644 index 0000000..1726467 --- /dev/null +++ b/magento2/Model/Config/Source/Dropdown/Dpr.php @@ -0,0 +1,22 @@ + '1.0', + 'label' => '1.0', + ), + array( + 'value' => '2.0', + 'label' => '2.0', + ), + ); + } +} diff --git a/magento2/Model/Config/Source/Dropdown/Gravity.php b/magento2/Model/Config/Source/Dropdown/Gravity.php new file mode 100644 index 0000000..b1be8a9 --- /dev/null +++ b/magento2/Model/Config/Source/Dropdown/Gravity.php @@ -0,0 +1,70 @@ + '', + 'label' => 'Default', + ), + array( + 'value' => 'face', + 'label' => 'Face', + ), + array( + 'value' => 'faces', + 'label' => 'Faces', + ), + array( + 'value' => 'north_west', + 'label' => 'North West', + ), + array( + 'value' => 'north', + 'label' => 'North', + ), + array( + 'value' => 'north_east', + 'label' => 'North East', + ), + array( + 'value' => 'east', + 'label' => 'East', + ), + array( + 'value' => 'center', + 'label' => 'Center', + ), + array( + 'value' => 'west', + 'label' => 'West', + ), + array( + 'value' => 'south_west', + 'label' => 'South West', + ), + array( + 'value' => 'south', + 'label' => 'South', + ), + array( + 'value' => 'south_east', + 'label' => 'South East', + ), + array( + 'value' => 'face:center', + 'label' => 'Face (Center)', + ), + array( + 'value' => 'faces:center', + 'label' => 'Faces (Center)', + ), + ); + } +} diff --git a/magento2/Model/Config/Source/Dropdown/Quality.php b/magento2/Model/Config/Source/Dropdown/Quality.php new file mode 100644 index 0000000..1f3fd38 --- /dev/null +++ b/magento2/Model/Config/Source/Dropdown/Quality.php @@ -0,0 +1,50 @@ + '20', + 'label' => '20%', + ), + array( + 'value' => '30', + 'label' => '30%', + ), + array( + 'value' => '40', + 'label' => '40%', + ), + array( + 'value' => '50', + 'label' => '50%', + ), + array( + 'value' => '60', + 'label' => '60%', + ), + array( + 'value' => '70', + 'label' => '70%', + ), + array( + 'value' => '80', + 'label' => '80%', + ), + array( + 'value' => '90', + 'label' => '90%', + ), + array( + 'value' => '100', + 'label' => '100%', + ), + ); + } +} diff --git a/magento2/Model/Configuration.php b/magento2/Model/Configuration.php new file mode 100644 index 0000000..8189d0f --- /dev/null +++ b/magento2/Model/Configuration.php @@ -0,0 +1,202 @@ +configReader = $configReader; + $this->configWriter = $configWriter; + $this->decryptor = $decryptor; + } + + /** + * @return Cloud + */ + public function getCloud() + { + return $this->getEnvironmentVariable()->getCloud(); + } + + /** + * @return Credentials + */ + public function getCredentials() + { + return $this->getEnvironmentVariable()->getCredentials(); + } + + /** + * @return Transformation + */ + public function getDefaultTransformation() + { + return Transformation::builder() + ->withGravity(Gravity::fromString($this->getDefaultGravity())) + ->withQuality(Quality::fromString($this->getImageQuality())) + ->withDpr(Dpr::fromString($this->getImageDpr())); + } + + /** + * @return boolean + */ + public function getCdnSubdomainStatus() + { + return $this->configReader->isSetFlag(self::CONFIG_CDN_SUBDOMAIN); + } + + /** + * @return string + */ + public function getUserPlatform() + { + return sprintf(self::USER_PLATFORM_TEMPLATE, '1.0.0', '2.0.0'); + } + + /** + * @return UploadConfig + */ + public function getUploadConfig() + { + return UploadConfig::fromBooleanValues(self::USE_FILENAME, self::UNIQUE_FILENAME, self::OVERWRITE); + } + + /** + * @return boolean + */ + public function isEnabled() + { + return $this->configReader->isSetFlag(self::CONFIG_PATH_ENABLED); + } + + public function enable() + { + $this->configWriter->save(self::CONFIG_PATH_ENABLED, self::SCOPE_ID_ONE); + } + + public function disable() + { + $this->configWriter->save(self::CONFIG_PATH_ENABLED, self::SCOPE_ID_ZERO); + } + + /** + * @return array + */ + public function getFormatsToPreserve() + { + return ['png', 'webp', 'gif', 'svg']; + } + + public function getMigratedPath($file) + { + return $file; + } + + /** + * @return string + */ + public function getDefaultGravity() + { + return (string) $this->configReader->getValue(self::CONFIG_DEFAULT_GRAVITY); + } + + /** + * @return string + */ + public function getFetchFormat() + { + if ($this->configReader->isSetFlag(self::CONFIG_DEFAULT_FETCH_FORMAT)) { + return FetchFormat::FETCH_FORMAT_AUTO; + } + return ''; + } + + /** + * @return string + */ + public function getImageQuality() + { + return $this->configReader->getValue(self::CONFIG_DEFAULT_QUALITY); + } + + /** + * @return string + */ + public function getImageDpr() + { + return $this->configReader->getValue(self::CONFIG_DEFAULT_DPR); + } + + /** + * @return CloudinaryEnvironmentVariable + */ + private function getEnvironmentVariable() + { + if (is_null($this->environmentVariable)) { + $this->environmentVariable = CloudinaryEnvironmentVariable::fromString( + $this->decryptor->decrypt( + $this->configReader->getValue(self::CONFIG_PATH_ENVIRONMENT_VARIABLE) + ) + ); + } + return $this->environmentVariable; + } +} diff --git a/magento2/Model/ImageRepository.php b/magento2/Model/ImageRepository.php new file mode 100644 index 0000000..9fc2e60 --- /dev/null +++ b/magento2/Model/ImageRepository.php @@ -0,0 +1,82 @@ +mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA); + $this->synchronizationChecker = $synchronizationChecker; + } + + /** + * @return array + */ + public function findUnsynchronisedImages() + { + $images = []; + + foreach ($this->getRecursiveIterator($this->mediaDirectory->getAbsolutePath()) as $item) { + $absolutePath = $item->getRealPath(); + $relativePath = $this->mediaDirectory->getRelativePath($item->getRealPath()); + if ($this->isValidImageFile($item) && !$this->synchronizationChecker->isSynchronized($relativePath)) { + $images[] = Image::fromPath($absolutePath, $relativePath); + } + } + + return $images; + } + + /** + * @param $directory + * @return \RecursiveIteratorIterator + */ + private function getRecursiveIterator($directory) + { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory), + \RecursiveIteratorIterator::SELF_FIRST + ); + } + + /** + * @param $item + * @return bool + */ + private function isValidImageFile($item) + { + return $item->isFile() && + strpos($item->getRealPath(), 'cache') === false && + strpos($item->getRealPath(), 'tmp') === false && + preg_match( + sprintf('#^[a-z0-9\.\-\_]+\.(?:%s)$#i', implode('|', $this->allowedImgExtensions)), + $item->getFilename() + ); + } +} diff --git a/magento2/Model/MigrationTask.php b/magento2/Model/MigrationTask.php new file mode 100644 index 0000000..0d8908d --- /dev/null +++ b/magento2/Model/MigrationTask.php @@ -0,0 +1,47 @@ +flagDir = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + } + + public function hasStarted() + { + return $this->flagDir->isExist(self::MIGRATION_RUNNING_FLAG_FILENAME); + } + + public function hasBeenStopped() + { + return !$this->hasStarted(); + } + + public function stop() + { + $this->flagDir->delete(self::MIGRATION_RUNNING_FLAG_FILENAME); + } + + public function start() + { + $this->flagDir->touch(self::MIGRATION_RUNNING_FLAG_FILENAME); + } +} diff --git a/magento2/Model/Observer/DeleteProductImage.php b/magento2/Model/Observer/DeleteProductImage.php new file mode 100644 index 0000000..d964047 --- /dev/null +++ b/magento2/Model/Observer/DeleteProductImage.php @@ -0,0 +1,45 @@ +productImageFinder = $productImageFinder; + $this->cloudinaryImageManager = $cloudinaryImageManager; + } + + /** + * @param Observer $observer + */ + public function execute(Observer $observer) + { + $product = $observer->getEvent()->getProduct(); + + foreach ($this->productImageFinder->findDeletedImages($product) as $image) { + $this->cloudinaryImageManager->removeAndUnSynchronise($image); + } + } +} diff --git a/magento2/Model/Observer/UploadProductImage.php b/magento2/Model/Observer/UploadProductImage.php new file mode 100644 index 0000000..3f699be --- /dev/null +++ b/magento2/Model/Observer/UploadProductImage.php @@ -0,0 +1,45 @@ +productImageFinder = $productImageFinder; + $this->cloudinaryImageManager = $cloudinaryImageManager; + } + + /** + * @param Observer $observer + */ + public function execute(Observer $observer) + { + $product = $observer->getEvent()->getProduct(); + + foreach ($this->productImageFinder->findNewImages($product) as $image) { + $this->cloudinaryImageManager->uploadAndSynchronise($image); + } + } +} diff --git a/magento2/Model/ProductImageFinder.php b/magento2/Model/ProductImageFinder.php new file mode 100644 index 0000000..bc4483f --- /dev/null +++ b/magento2/Model/ProductImageFinder.php @@ -0,0 +1,63 @@ +imageCreator = $imageCreator; + } + + /** + * @param Product $product + * + * @return \CloudinaryExtension\Image[] + */ + public function findNewImages(Product $product) + { + return $this->find($product, new NewImageFilter()); + } + + /** + * @param Product $product + * + * @return \CloudinaryExtension\Image[] + */ + public function findDeletedImages(Product $product) + { + return $this->find($product, new DeletedImageFilter()); + } + + /** + * @param Product $product + * @param ImageFilter $filter + * + * @return \CloudinaryExtension\Image[] + */ + private function find(Product $product, ImageFilter $filter) + { + return array_map($this->imageCreator, array_filter( + $product->getMediaGallery('images'), + $filter + )); + } +} diff --git a/magento2/Model/ProductImageFinder/DeletedImageFilter.php b/magento2/Model/ProductImageFinder/DeletedImageFilter.php new file mode 100644 index 0000000..32e8f0c --- /dev/null +++ b/magento2/Model/ProductImageFinder/DeletedImageFilter.php @@ -0,0 +1,15 @@ +mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA); + $this->baseMediaPath = $mediaConfig->getBaseMediaPath(); + } + + /** + * @param array $imageData + * + * @return Image + */ + public function __invoke(array $imageData) + { + $fullPath = $this->baseMediaPath . $imageData['file']; + + return Image::fromPath( + $this->mediaDirectory->getAbsolutePath($fullPath), + $fullPath + ); + } +} \ No newline at end of file diff --git a/magento2/Model/ProductImageFinder/ImageFilter.php b/magento2/Model/ProductImageFinder/ImageFilter.php new file mode 100644 index 0000000..48670a6 --- /dev/null +++ b/magento2/Model/ProductImageFinder/ImageFilter.php @@ -0,0 +1,16 @@ +_init('cloudinary_synchronisation', 'cloudinary_synchronisation_id'); + } +} diff --git a/magento2/Model/ResourceModel/Synchronisation/Collection.php b/magento2/Model/ResourceModel/Synchronisation/Collection.php new file mode 100644 index 0000000..6a4be56 --- /dev/null +++ b/magento2/Model/ResourceModel/Synchronisation/Collection.php @@ -0,0 +1,15 @@ +_init(SynchronisationModel::class, SynchronisationResourceModel::class); + } +} diff --git a/magento2/Model/Synchronisation.php b/magento2/Model/Synchronisation.php new file mode 100644 index 0000000..f1c16fd --- /dev/null +++ b/magento2/Model/Synchronisation.php @@ -0,0 +1,40 @@ +_init(SynchronisationResourceModel::class); + } + + public function setImagePath($imagePath) + { + return $this->setData('image_path', $imagePath); + } + + public function getImagePath() + { + return $this->getData('image_path'); + } + + public function getFilename() + { + return basename($this->getImagePath()); + } + + public function getRelativePath() + { + return $this->getImagePath(); + } + + public function tagAsSynchronized() + { + $this->save(); + } +} diff --git a/magento2/Model/SynchronisationChecker.php b/magento2/Model/SynchronisationChecker.php new file mode 100644 index 0000000..bf37a30 --- /dev/null +++ b/magento2/Model/SynchronisationChecker.php @@ -0,0 +1,35 @@ +synchronisationRepository = $synchronisationRepository; + } + + /** + * @param $imageName + * @return bool + */ + public function isSynchronized($imageName) + { + if (!$imageName) { + return false; + } + + return $this->synchronisationRepository->getListByImagePath($imageName)->getTotalCount() > 0; + } +} diff --git a/magento2/Model/SynchronisationRepository.php b/magento2/Model/SynchronisationRepository.php new file mode 100644 index 0000000..22a080e --- /dev/null +++ b/magento2/Model/SynchronisationRepository.php @@ -0,0 +1,203 @@ +filterBuilder = $filterBuilder; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->collectionFactory = $collectionFactory; + $this->searchResult = $searchResult; + $this->searchResultsFactory = $searchResultsFactory; + $this->synchronisationFactory = $synchronisationFactory; + } + + /** + * Retrieve data which match a specified criteria. + * + * @api + * + * @param SearchCriteriaInterface $searchCriteria + * @return SearchResultsInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria) + { + $collection = $this->collectionFactory->create(); + + $this->setFilters($searchCriteria, $collection); + $this->setSortOrder($searchCriteria, $collection); + $this->setPageSize($searchCriteria, $collection); + $this->setCurrentPage($searchCriteria, $collection); + + $searchResult = $this->searchResultsFactory->create(); + $searchResult->setSearchCriteria($searchCriteria); + $searchResult->setItems($collection->getItems()); + $searchResult->setTotalCount($collection->getSize()); + + return $searchResult; + } + + /** + * @param string $imagePath + * + * @return SearchResultsInterface + */ + public function getListByImagePath($imagePath) + { + $this->searchCriteriaBuilder->addFilters([$this->createImagePathFilter($imagePath)]); + + return $this->getList($this->searchCriteriaBuilder->create()); + } + + /** + * @param string $imagePath + */ + public function saveAsSynchronized($imagePath) + { + $this->synchronisationFactory->create() + ->setImagePath($imagePath) + ->tagAsSynchronized(); + } + + /** + * @param string $imagePath + */ + public function removeSynchronised($imagePath) + { + $result = $this->getListByImagePath($imagePath); + + foreach ($result->getItems() as $item) { + $item->delete(); + } + } + + /** + * Create image name filter + * + * @param string $imagePath + * @return \Magento\Framework\Api\Filter + */ + private function createImagePathFilter($imagePath) + { + $this->filterBuilder->setField('image_path'); + $this->filterBuilder->setConditionType('eq'); + $this->filterBuilder->setValue($imagePath); + + return $this->filterBuilder->create(); + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @param SynchronisationCollection $collection + */ + private function setFilters(SearchCriteriaInterface $searchCriteria, $collection) + { + foreach ($searchCriteria->getFilterGroups() as $filterGroup) { + foreach ($filterGroup->getFilters() as $filter) { + $collection->addFieldToFilter( + $filter->getField(), + [$filter->getConditionType() => $filter->getValue()] + ); + } + } + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @param SynchronisationCollection $collection + */ + private function setSortOrder(SearchCriteriaInterface $searchCriteria, $collection) + { + if ($searchCriteria->getSortOrders()) { + foreach ($searchCriteria->getSortOrders() as $sortOrder) { + $collection->addOrder( + $sortOrder->getField(), + $sortOrder->getDirection() === SearchCriteriaInterface::SORT_ASC ? 'ASC' : 'DESC' + ); + } + } + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @param SynchronisationCollection $collection + */ + private function setPageSize(SearchCriteriaInterface $searchCriteria, $collection) + { + if ($searchCriteria->getPageSize()) { + $collection->setPageSize($searchCriteria->getPageSize()); + } + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @param SynchronisationCollection $collection + */ + private function setCurrentPage(SearchCriteriaInterface $searchCriteria, $collection) + { + if ($searchCriteria->getCurrentPage()) { + $collection->setCurPage($searchCriteria->getCurrentPage()); + } + } +} diff --git a/magento2/Model/Template/Filter.php b/magento2/Model/Template/Filter.php new file mode 100644 index 0000000..0f41e69 --- /dev/null +++ b/magento2/Model/Template/Filter.php @@ -0,0 +1,106 @@ +imageFactory = $imageFactory; + $this->urlGenerator = $urlGenerator; + + parent::__construct( + $string, + $logger, + $escaper, + $assetRepo, + $scopeConfig, + $coreVariableFactory, + $storeManager, + $layout, + $layoutFactory, + $appState, + $urlModel, + $emogrifier, + $configVariables, + $widgetResource, + $widget + ); + } + + /** + * Retrieve media file URL directive + * + * @param string[] $construction + * @return string + */ + public function mediaDirective($construction) + { + $params = $this->getParameters($construction[2]); + $storeManager = $this->_storeManager; + + $image = $this->imageFactory->build( + $params['url'], + function() use ($storeManager, $params) { + return sprintf( + '%s%s', + $storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA), + $params['url'] + ); + } + ); + + return $this->urlGenerator->generateFor($image); + } +} diff --git a/magento2/Plugin/FileRemover.php b/magento2/Plugin/FileRemover.php new file mode 100644 index 0000000..8b31453 --- /dev/null +++ b/magento2/Plugin/FileRemover.php @@ -0,0 +1,50 @@ +cloudinaryImageManager = $cloudinaryImageManager; + $this->mediaDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA); + } + + /** + * Delete file (and its thumbnail if exists) from storage + * + * @param string $target File path to be deleted + * @return $this + */ + public function beforeDeleteFile(Storage $storage, $target) + { + $this->cloudinaryImageManager->removeAndUnSynchronise( + Image::fromPath($target, $this->mediaDirectory->getRelativePath($target)) + ); + + return [$target]; + } +} diff --git a/magento2/Plugin/FileUploader.php b/magento2/Plugin/FileUploader.php new file mode 100644 index 0000000..8992fb6 --- /dev/null +++ b/magento2/Plugin/FileUploader.php @@ -0,0 +1,81 @@ +cloudinaryImageManager = $cloudinaryImageManager; + $this->directoryList = $directoryList; + } + + /** + * @param Uploader $uploader + * @param array $result + * @return array + */ + public function afterSave(Uploader $uploader, $result) + { + $filepath = $this->absolutePath($result); + + if (!$this->isTemporaryPath($filepath)) { + + $this->cloudinaryImageManager->uploadAndSynchronise( + Image::fromPath($filepath, $this->mediaRelativePath($filepath)) + ); + + } + + return $result; + } + + /** + * @param string $filepath + * @return bool + */ + protected function isTemporaryPath($filepath) + { + return strpos($filepath, sprintf('%s/tmp', $this->directoryList->getPath('media'))) === 0; + } + + /** + * @param array $result + * @return string + */ + protected function absolutePath(array $result) + { + return sprintf('%s%s%s', $result['path'], DIRECTORY_SEPARATOR, $result['file']); + } + + /** + * @param string $filepath + * @return string + */ + protected function mediaRelativePath($filepath) + { + $mediaPath = $this->directoryList->getPath('media') . DIRECTORY_SEPARATOR; + return (strpos($filepath, $mediaPath) === 0) ? str_replace($mediaPath, '', $filepath) : $filepath; + } +} diff --git a/magento2/Plugin/ImageHelper.php b/magento2/Plugin/ImageHelper.php new file mode 100644 index 0000000..7858d7f --- /dev/null +++ b/magento2/Plugin/ImageHelper.php @@ -0,0 +1,145 @@ +imageFactory = $imageFactory; + $this->urlGenerator = $urlGenerator; + $this->configuration = $configuration; + $this->dimensions = null; + $this->imageFile = null; + } + + /** + * @param CatalogImageHelper $helper + * @param ProductInterface $product + * @param string $imageId + * @param array $attributes + * + * @return array + */ + public function beforeInit(CatalogImageHelper $helper, $product, $imageId, $attributes = []) + { + $this->product = $product; + $this->dimensions = null; + $this->imageFile = null; + $this->keepFrame = true; + return [$product, $imageId, $attributes]; + } + + /** + * @param CatalogImageHelper $helper + * @param string $file + * + * @return string[] + */ + public function beforeSetImageFile(CatalogImageHelper $helper, $file) + { + $this->imageFile = $file; + return [$file]; + } + + /** + * @param CatalogImageHelper $helper + * @param int $width + * @param int $height + * + * @return array + */ + public function beforeResize(CatalogImageHelper $helper, $width, $height = null) + { + $this->dimensions = Dimensions::fromWidthAndHeight($width, $height); + + return [$width, $height]; + } + + /** + * @param CatalogImageHelper $helper + * @param bool $flag + */ + public function beforeKeepFrame(CatalogImageHelper $helper, $flag) + { + $this->keepFrame = (bool)$flag; + } + + /** + * @param CatalogImageHelper $helper + * @param \Closure $originalMethod + * + * @return string + */ + public function aroundGetUrl(CatalogImageHelper $helper, \Closure $originalMethod) + { + $image = $this->imageFactory->build( + sprintf('catalog/product%s', $this->imageFile ?: $this->product->getData($helper->getType())), + $originalMethod + ); + + $dimensions = $this->dimensions ?: Dimensions::fromWidthAndHeight($helper->getWidth(), $helper->getHeight()); + + $transform = $this->configuration->getDefaultTransformation()->withDimensions($dimensions); + + if ($this->keepFrame) { + $transform->withCrop(Crop::fromString('pad')) + ->withDimensions(Dimensions::squareMissingDimension($dimensions)); + } else { + $transform->withCrop(Crop::fromString('fit')); + } + + return $this->urlGenerator->generateFor($image, $transform); + } +} diff --git a/magento2/Plugin/MediaConfig.php b/magento2/Plugin/MediaConfig.php new file mode 100644 index 0000000..5dd30b2 --- /dev/null +++ b/magento2/Plugin/MediaConfig.php @@ -0,0 +1,47 @@ +imageFactory = $imageFactory; + $this->urlGenerator = $urlGenerator; + } + + /** + * @param CatalogMediaConfig $mediaConfig + * @param \Closure $originalMethod + * @param string $file + * + * @return string + */ + public function aroundGetMediaUrl(CatalogMediaConfig $mediaConfig, \Closure $originalMethod, $file) + { + $image = $this->imageFactory->build( + $mediaConfig->getBaseMediaPath() . $file, + function() use ($originalMethod, $file) { return $originalMethod($file); } + ); + + return $this->urlGenerator->generateFor($image); + } +} diff --git a/magento2/README.md b/magento2/README.md new file mode 100644 index 0000000..69a39e9 --- /dev/null +++ b/magento2/README.md @@ -0,0 +1 @@ +# cloudinary-magento2 \ No newline at end of file diff --git a/magento2/Setup/InstallSchema.php b/magento2/Setup/InstallSchema.php new file mode 100644 index 0000000..500da40 --- /dev/null +++ b/magento2/Setup/InstallSchema.php @@ -0,0 +1,43 @@ +startSetup(); + + /** + * Create table 'cloudinary_synchronisation' + */ + $table = $setup->getConnection()->newTable( + $setup->getTable('cloudinary_synchronisation') + )->addColumn( + 'cloudinary_synchronisation_id', + Table::TYPE_INTEGER, + null, + ['identity' => true, 'nullable' => false, 'primary' => true, 'unsigned' => true], + 'Cloudinary Synchronisation ID' + )->addColumn( + 'image_path', + Table::TYPE_TEXT, + 255, + ['nullable' => false], + 'Image Path' + ); + + $setup->getConnection()->createTable($table); + + $setup->endSetup(); + } +} diff --git a/magento2/behat.yml b/magento2/behat.yml new file mode 100644 index 0000000..4101e21 --- /dev/null +++ b/magento2/behat.yml @@ -0,0 +1,41 @@ +default: + suites: + ui: + paths: + features: ../cloudinary-core/features + bootstrap: features/bootstrap + filters: { tags: '@critical,@ui' } + contexts: + - FeatureContext + + extensions: + Bex\Behat\Magento2InitExtension: + magento_bootstrap_path: /vagrant/app/bootstrap.php + Bex\Behat\BrowserInitialiserExtension: + close_browser_after_scenario: true + browser_window_size: max + Bex\Behat\Magento2InitExtension: + magento_configs: + - + path: 'admin/security/use_form_key' + value: 0 + Bex\Behat\ScreenshotExtension: + image_drivers: + local: + screenshot_directory: /vagrant + SensioLabs\Behat\PageObjectExtension: ~ + Behat\MinkExtension\ServiceContainer\MinkExtension: + base_url: 'http://magento2.dev/' + goutte: + guzzle_parameters: + curl.options: + CURLOPT_SSL_VERIFYPEER: false + CURLOPT_CERTINFO: false + CURLOPT_TIMEOUT: 120 + ssl.certificate_authority: false + selenium2: + wd_host: http://localhost:4444/wd/hub + browser: phantomjs +# command to open the failing html pages: + show_cmd: echo '%s' + show_tmp_dir: /vagrant diff --git a/magento2/composer.json b/magento2/composer.json new file mode 100644 index 0000000..a49a4a2 --- /dev/null +++ b/magento2/composer.json @@ -0,0 +1,53 @@ +{ + "name": "inviqa/cloudinary-magento2", + "description": "Cloudinary Magento 2 Integration.", + "type": "magento2-module", + "version": "1.0.0", + "minimum-stability": "dev", + "require": { + "php": ">=5.5", + "inviqa/cloudinary-core": "1.1.0" + }, + "require-dev": { + "phpspec/phpspec": "^3.0", + "behat/behat": "~3.0.15", + "sensiolabs/behat-page-object-extension": "2.0.*@dev", + "behat/mink-selenium2-driver": "*", + "behat/mink-goutte-driver": "^1.0", + "squizlabs/php_codesniffer": "1.*", + "phpmd/phpmd": "1.*", + "sebastian/phpcpd": "2.*", + "pdepend/pdepend": "1.*", + "phploc/phploc": "2.*", + "theseer/phpdox": "0.6.*", + "theseer/fxsl": "1.0.*@dev", + "covex-nn/phpcb": "1.0.*@dev", + "bossa/phpspec2-expect": "^2.0", + "bex/behat-magento2-init": "dev-master", + "bex/behat-browser-initialiser": "^1.0", + "bex/behat-screenshot": "^1.0" + }, + "config": { + "bin-dir": "bin", + "use-include-path": true + }, + "repositories": [ + { + "type": "git", + "url": "git@github.com:inviqa/cloudinary-core.git" + } + ], + "autoload-dev": { + "psr-0": { + "": [ + "features/bootstrap", + "features/fixtures", + "/app/app/code/" + ] + }, + "psr-4": { + "Magento\\Framework\\": "/app/vendor/magento/framework/", + "Magento\\Catalog\\": "/app/vendor/magento/module-catalog" + } + } +} diff --git a/magento2/composer.lock b/magento2/composer.lock new file mode 100644 index 0000000..61e4a38 --- /dev/null +++ b/magento2/composer.lock @@ -0,0 +1,3899 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "1e3dc60115a4782ab9fb942f3364f13c", + "content-hash": "104b0bac1900d9c84ff5e0f9476c8f92", + "packages": [ + { + "name": "cloudinary/cloudinary_php", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/cloudinary/cloudinary_php.git", + "reference": "8b89be228b39bcdb36d5e642e9796c756760737e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cloudinary/cloudinary_php/zipball/8b89be228b39bcdb36d5e642e9796c756760737e", + "reference": "8b89be228b39bcdb36d5e642e9796c756760737e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.7.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cloudinary", + "homepage": "https://github.com/cloudinary/cloudinary_php/graphs/contributors" + } + ], + "description": "Cloudinary PHP SDK", + "homepage": "https://github.com/cloudinary/cloudinary_php", + "keywords": [ + "cdn", + "cloud", + "cloudinary", + "image management", + "sdk" + ], + "time": "2017-02-23 01:10:18" + }, + { + "name": "inviqa/cloudinary-core", + "version": "1.2.1", + "source": { + "type": "git", + "url": "git@github.com:inviqa/cloudinary-core.git", + "reference": "315636abf5d9fe2c10dd06eae5b2684c4d980603" + }, + "require": { + "cloudinary/cloudinary_php": "~1.6.0", + "php": ">=5.4.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "^1.0", + "behat/mink-selenium2-driver": "*", + "bossa/phpspec2-expect": "1.0.3", + "mayflower/php-codebrowser": "^1.1", + "phpspec/phpspec": "^2.4.0", + "phpunit/phpunit": "3.7.*", + "sensiolabs/behat-page-object-extension": "*@dev", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "cloudinary-core", + "autoload": { + "psr-0": { + "": [ + "features/bootstrap", + "lib" + ] + } + }, + "license": [ + "proprietary" + ], + "description": "Cloudinary Core.", + "time": "2017-03-23 15:22:25" + } + ], + "packages-dev": [ + { + "name": "behat/behat", + "version": "v3.0.15", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "b35ae3d45332d80c532af69cc36f780a9397a996" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996", + "reference": "b35ae3d45332d80c532af69cc36f780a9397a996", + "shasum": "" + }, + "require": { + "behat/gherkin": "~4.3", + "behat/transliterator": "~1.0", + "ext-mbstring": "*", + "php": ">=5.3.3", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.3", + "symfony/console": "~2.1", + "symfony/dependency-injection": "~2.1", + "symfony/event-dispatcher": "~2.1", + "symfony/translation": "~2.3", + "symfony/yaml": "~2.1" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "~1.0", + "phpunit/phpunit": "~4.0", + "symfony/process": "~2.1" + }, + "suggest": { + "behat/mink-extension": "for integration with Mink testing framework", + "behat/symfony2-extension": "for integration with Symfony2 web framework", + "behat/yii-extension": "for integration with Yii web framework" + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Behat": "src/", + "Behat\\Testwork": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "Agile", + "BDD", + "ScenarioBDD", + "Scrum", + "StoryBDD", + "User story", + "business", + "development", + "documentation", + "examples", + "symfony", + "testing" + ], + "time": "2015-02-22 14:10:33" + }, + { + "name": "behat/gherkin", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2016-10-30 11:50:56" + }, + { + "name": "behat/mink", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/minkphp/Mink.git", + "reference": "9ea1cebe3dc529ba3861d87c818f045362c40484" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/Mink/zipball/9ea1cebe3dc529ba3861d87c818f045362c40484", + "reference": "9ea1cebe3dc529ba3861d87c818f045362c40484", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/css-selector": "~2.1|~3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0" + }, + "suggest": { + "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)", + "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation", + "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", + "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Browser controller/emulator abstraction for PHP", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "testing", + "web" + ], + "time": "2017-02-06 09:59:54" + }, + { + "name": "behat/mink-browserkit-driver", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkBrowserKitDriver.git", + "reference": "1c9c8ad8838af33448d10baa57658b4cb55f23d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/1c9c8ad8838af33448d10baa57658b4cb55f23d6", + "reference": "1c9c8ad8838af33448d10baa57658b4cb55f23d6", + "shasum": "" + }, + "require": { + "behat/mink": "^1.7.1@dev", + "php": ">=5.3.6", + "symfony/browser-kit": "~2.3|~3.0", + "symfony/dom-crawler": "~2.3|~3.0" + }, + "require-dev": { + "mink/driver-testsuite": "dev-master", + "symfony/http-kernel": "~2.3|~3.0" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Symfony2 BrowserKit driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "Mink", + "Symfony2", + "browser", + "testing" + ], + "time": "2016-10-03 08:27:03" + }, + { + "name": "behat/mink-extension", + "version": "v2.2", + "source": { + "type": "git", + "url": "https://github.com/Behat/MinkExtension.git", + "reference": "5b4bda64ff456104564317e212c823e45cad9d59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59", + "reference": "5b4bda64ff456104564317e212c823e45cad9d59", + "shasum": "" + }, + "require": { + "behat/behat": "~3.0,>=3.0.5", + "behat/mink": "~1.5", + "php": ">=5.3.2", + "symfony/config": "~2.2|~3.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "~1.1", + "phpspec/phpspec": "~2.0" + }, + "type": "behat-extension", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\MinkExtension": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + }, + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com" + } + ], + "description": "Mink extension for Behat", + "homepage": "http://extensions.behat.org/mink", + "keywords": [ + "browser", + "gui", + "test", + "web" + ], + "time": "2016-02-15 07:55:18" + }, + { + "name": "behat/mink-goutte-driver", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkGoutteDriver.git", + "reference": "7a4b2d49511865e23d61463514fa2754d42ec658" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/7a4b2d49511865e23d61463514fa2754d42ec658", + "reference": "7a4b2d49511865e23d61463514fa2754d42ec658", + "shasum": "" + }, + "require": { + "behat/mink-browserkit-driver": "~1.2@dev", + "fabpot/goutte": "~1.0.4|~2.0|~3.1", + "php": ">=5.3.1" + }, + "require-dev": { + "mink/driver-testsuite": "dev-master" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Goutte driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "browser", + "goutte", + "headless", + "testing" + ], + "time": "2016-10-14 20:19:06" + }, + { + "name": "behat/mink-selenium2-driver", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/minkphp/MinkSelenium2Driver.git", + "reference": "739b7570f0536bad9b07b511a62c885ee1ec029a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/739b7570f0536bad9b07b511a62c885ee1ec029a", + "reference": "739b7570f0536bad9b07b511a62c885ee1ec029a", + "shasum": "" + }, + "require": { + "behat/mink": "~1.7@dev", + "instaclick/php-webdriver": "~1.1", + "php": ">=5.3.1" + }, + "require-dev": { + "mink/driver-testsuite": "dev-master" + }, + "type": "mink-driver", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Mink\\Driver\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Pete Otaqui", + "email": "pete@otaqui.com", + "homepage": "https://github.com/pete-otaqui" + } + ], + "description": "Selenium2 (WebDriver) driver for Mink framework", + "homepage": "http://mink.behat.org/", + "keywords": [ + "ajax", + "browser", + "javascript", + "selenium", + "testing", + "webdriver" + ], + "time": "2017-02-06 08:22:23" + }, + { + "name": "behat/transliterator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "02fa32d26934f99cb8a3eff2fe065a1fd53f847b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/02fa32d26934f99cb8a3eff2fe065a1fd53f847b", + "reference": "02fa32d26934f99cb8a3eff2fe065a1fd53f847b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "chuyskywalker/rolling-curl": "^3.1", + "php-yaoi/php-yaoi": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Transliterator": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "time": "2016-10-21 08:46:05" + }, + { + "name": "bex/behat-browser-initialiser", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/tkotosz/behat-browser-initialiser.git", + "reference": "8fe7d7771159673d13300b4c05102746a8dbf07c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tkotosz/behat-browser-initialiser/zipball/8fe7d7771159673d13300b4c05102746a8dbf07c", + "reference": "8fe7d7771159673d13300b4c05102746a8dbf07c", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.0", + "behat/mink-extension": "^2.0.0", + "php": ">=5.4" + }, + "require-dev": { + "behat/mink-selenium2-driver": "^1.3.0", + "bex/behat-test-runner": "^1.2.1", + "jakoch/phantomjs-installer": "^2.1.1", + "phpspec/phpspec": "^2.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tibor Kotosz", + "email": "kotosy@gmail.com", + "homepage": "https://github.com/tkotosz", + "role": "Developer" + } + ], + "description": "Extension for behat to help configure the browser", + "homepage": "https://github.com/tkotosz/behat-browser-initialiser", + "keywords": [ + "BDD", + "Behat", + "TDD" + ], + "time": "2016-06-13 11:09:22" + }, + { + "name": "bex/behat-extension-driver-locator", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/tkotosz/behat-extension-driver-locator.git", + "reference": "af9fb11f5f3cc220ee2c08071ee9d50f11048b86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tkotosz/behat-extension-driver-locator/zipball/af9fb11f5f3cc220ee2c08071ee9d50f11048b86", + "reference": "af9fb11f5f3cc220ee2c08071ee9d50f11048b86", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.0", + "php": ">=5.4" + }, + "require-dev": { + "phpspec/phpspec": "2.4.0-alpha2" + }, + "type": "library", + "autoload": { + "psr-0": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tibor Kotosz", + "email": "kotosy@gmail.com", + "homepage": "https://github.com/tkotosz", + "role": "Developer" + } + ], + "description": "Driver locator tool for behat extensions", + "homepage": "https://github.com/tkotosz/behat-extension-driver-locator", + "keywords": [ + "BDD", + "Behat", + "TDD" + ], + "time": "2015-12-17 13:26:09" + }, + { + "name": "bex/behat-magento2-init", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/tkotosz/behat-magento2-init.git", + "reference": "7ed7fe215d3cf64c6827c604dae83bb1288633f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tkotosz/behat-magento2-init/zipball/7ed7fe215d3cf64c6827c604dae83bb1288633f7", + "reference": "7ed7fe215d3cf64c6827c604dae83bb1288633f7", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.0", + "php": ">=5.4" + }, + "type": "library", + "autoload": { + "psr-0": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tibor Kotosz", + "email": "kotosy@gmail.com", + "homepage": "https://github.com/tkotosz", + "role": "Developer" + } + ], + "description": "Provides access to magento2 object manager from behat and allows to change magento config settings temporarly", + "homepage": "https://github.com/tkotosz/behat-magento2-init", + "keywords": [ + "BDD", + "Behat", + "TDD", + "magento2" + ], + "time": "2017-01-06 11:09:19" + }, + { + "name": "bex/behat-screenshot", + "version": "1.2.6", + "source": { + "type": "git", + "url": "https://github.com/elvetemedve/behat-screenshot.git", + "reference": "1e2f704a5dd26b679953d6aff9a1add7ec978f81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elvetemedve/behat-screenshot/zipball/1e2f704a5dd26b679953d6aff9a1add7ec978f81", + "reference": "1e2f704a5dd26b679953d6aff9a1add7ec978f81", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.0", + "behat/mink-extension": "^2.0.0", + "bex/behat-extension-driver-locator": "^1.0.2", + "php": ">=5.4", + "symfony/filesystem": "^2.7|^3.0", + "symfony/finder": "^2.7|^3.0" + }, + "require-dev": { + "behat/mink-selenium2-driver": "^1.3.0", + "bex/behat-screenshot-image-driver-dummy": "^1.0", + "bex/behat-test-runner": "^1.2.1", + "jakoch/phantomjs-installer": "^2.1.1-p07", + "phpspec/phpspec": "^2.5" + }, + "suggest": { + "bex/behat-screenshot-image-driver-img42": "Allows to upload the screenshot to img42.com", + "bex/behat-screenshot-image-driver-unsee": "Allows to upload the screenshot to unsee.cc", + "bex/behat-screenshot-image-driver-uploadpie": "Allows to upload the screenshot to uploadpie.com" + }, + "type": "library", + "autoload": { + "psr-0": { + "Bex\\Behat\\ScreenshotExtension\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Geza Buza", + "email": "bghome@gmail.com", + "homepage": "https://twitter.com/medve540", + "role": "Developer" + }, + { + "name": "Tibor Kotosz", + "email": "kotosy@gmail.com", + "homepage": "https://github.com/tkotosz", + "role": "Developer" + } + ], + "description": "Extension for behat to help debug failing scenarios", + "homepage": "https://github.com/elvetemedve/behat-screenshot", + "keywords": [ + "BDD", + "Behat", + "TDD", + "behat-screenshot" + ], + "time": "2016-11-26 16:45:39" + }, + { + "name": "bossa/phpspec2-expect", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/BossaConsulting/phpspec2-expect.git", + "reference": "2a753ad2160a02725711c1cb4f4bf55d43512d60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BossaConsulting/phpspec2-expect/zipball/2a753ad2160a02725711c1cb4f4bf55d43512d60", + "reference": "2a753ad2160a02725711c1cb4f4bf55d43512d60", + "shasum": "" + }, + "require": { + "phpspec/phpspec": "~3.0 <3.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "type": "library", + "autoload": { + "files": [ + "expect.php" + ], + "psr-0": { + "Bossa\\PhpSpec\\Expect\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marcello Duarte", + "homepage": "http://marcelloduarte.net/" + } + ], + "description": "Helper that decorates any SUS with a phpspec lazy object wrapper", + "keywords": [ + "BDD", + "SpecBDD", + "TDD", + "spec", + "specification" + ], + "time": "2016-11-23 16:57:12" + }, + { + "name": "covex-nn/phpcb", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/covex-nn/PHP_CodeBrowser.git", + "reference": "f03291ba267baaad3ec1a3cde87ccedaa0b9fa35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/covex-nn/PHP_CodeBrowser/zipball/f03291ba267baaad3ec1a3cde87ccedaa0b9fa35", + "reference": "f03291ba267baaad3ec1a3cde87ccedaa0b9fa35", + "shasum": "" + }, + "require": { + "phpunit/php-file-iterator": "~1.3", + "symfony/console": ">=2.2.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "bin": [ + "bin/phpcb" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-fork": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "src/" + ], + "license": [ + "BSD-2-Clause" + ], + "description": "A code browser that augments the code with information from various QA tools.", + "abandoned": "mayflower/php-codebrowser", + "time": "2013-11-12 21:58:07" + }, + { + "name": "doctrine/instantiator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "5acd2bd8c2b600ad5cc4c9180ebf0a930604d6a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/5acd2bd8c2b600ad5cc4c9180ebf0a930604d6a5", + "reference": "5acd2bd8c2b600ad5cc4c9180ebf0a930604d6a5", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2017-02-16 16:15:51" + }, + { + "name": "fabpot/goutte", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Goutte.git", + "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/db5c28f4a010b4161d507d5304e28a7ebf211638", + "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "symfony/browser-kit": "~2.1|~3.0", + "symfony/css-selector": "~2.1|~3.0", + "symfony/dom-crawler": "~2.1|~3.0" + }, + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Goutte\\": "Goutte" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/FriendsOfPHP/Goutte", + "keywords": [ + "scraper" + ], + "time": "2017-01-03 13:21:43" + }, + { + "name": "guzzlehttp/guzzle", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "a1c4a74bf31d4e41d783fafb635c806cc19c2e9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a1c4a74bf31d4e41d783fafb635c806cc19c2e9b", + "reference": "a1c4a74bf31d4e41d783fafb635c806cc19c2e9b", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "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" + ], + "time": "2017-03-22 11:33:29" + }, + { + "name": "guzzlehttp/promises", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "73815f322dc2d2aa711f8af41f87d3e9d9246bae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/73815f322dc2d2aa711f8af41f87d3e9d9246bae", + "reference": "73815f322dc2d2aa711f8af41f87d3e9d9246bae", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + ], + "time": "2017-03-28 16:54:57" + }, + { + "name": "guzzlehttp/psr7", + "version": "dev-master", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + ], + "time": "2017-03-20 17:10:46" + }, + { + "name": "instaclick/php-webdriver", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/instaclick/php-webdriver.git", + "reference": "9c9836dc5f2dcb1db9a94dd2599aa1049fc64b13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/9c9836dc5f2dcb1db9a94dd2599aa1049fc64b13", + "reference": "9c9836dc5f2dcb1db9a94dd2599aa1049fc64b13", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "satooshi/php-coveralls": "^1.0||^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "WebDriver": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Justin Bishop", + "email": "jubishop@gmail.com", + "role": "Developer" + }, + { + "name": "Anthon Pang", + "email": "apang@softwaredevelopment.ca", + "role": "Fork Maintainer" + } + ], + "description": "PHP WebDriver for Selenium 2", + "homepage": "http://instaclick.com/", + "keywords": [ + "browser", + "selenium", + "webdriver", + "webtest" + ], + "time": "2017-03-22 17:46:51" + }, + { + "name": "nikic/php-parser", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "b5935a4aff9550c26c913a78079bdc34834bbfc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/b5935a4aff9550c26c913a78079bdc34834bbfc4", + "reference": "b5935a4aff9550c26c913a78079bdc34834bbfc4", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2017-03-17 10:35:48" + }, + { + "name": "ocramius/proxy-manager", + "version": "1.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/57e9272ec0e8deccf09421596e0e2252df440e11", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-code": ">2.2.5,<3.0" + }, + "require-dev": { + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "1.5.*" + }, + "suggest": { + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-stdlib": "To use the hydrator proxy", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "time": "2015-08-09 04:28:19" + }, + { + "name": "pdepend/pdepend", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "1537f19d62d7b30c13ac173270106df7c6b9c459" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/1537f19d62d7b30c13ac173270106df7c6b9c459", + "reference": "1537f19d62d7b30c13ac173270106df7c6b9c459", + "shasum": "" + }, + "require": { + "php": ">=5.2.3" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "autoload": { + "psr-0": { + "PHP_": "src/main/php/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "time": "2013-12-04 17:46:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "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.", + "time": "2016-09-30 07:12:33" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-11-25 06:54:22" + }, + { + "name": "phploc/phploc", + "version": "2.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phploc.git", + "reference": "50e063abd41833b3a5d29a2e8fbef5859ac28bdc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phploc/zipball/50e063abd41833b3a5d29a2e8fbef5859ac28bdc", + "reference": "50e063abd41833b3a5d29a2e8fbef5859ac28bdc", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/finder-facade": "~1.1", + "sebastian/git": "~2.0", + "sebastian/version": "~1.0.3", + "symfony/console": "~2.5" + }, + "require-dev": { + "phpunit/phpunit": "~4" + }, + "bin": [ + "phploc" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "A tool for quickly measuring the size of a PHP project.", + "homepage": "https://github.com/sebastianbergmann/phploc", + "time": "2015-10-22 13:44:19" + }, + { + "name": "phpmd/phpmd", + "version": "1.5.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "f2d47500f4c5f80ee442d95829c62c2ece2bbeb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/f2d47500f4c5f80ee442d95829c62c2ece2bbeb6", + "reference": "f2d47500f4c5f80ee442d95829c62c2ece2bbeb6", + "shasum": "" + }, + "require": { + "pdepend/pdepend": "1.1.*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.*@stable", + "squizlabs/php_codesniffer": "@stable" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "../../pdepend/pdepend/src/main/php", + "src/main/php" + ], + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of PHPMD handled with Composer.", + "time": "2014-09-16 14:26:49" + }, + { + "name": "phpspec/php-diff", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/php-diff.git", + "reference": "0464787bfa7cd13576c5a1e318709768798bec6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/php-diff/zipball/0464787bfa7cd13576c5a1e318709768798bec6a", + "reference": "0464787bfa7cd13576c5a1e318709768798bec6a", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Diff": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton" + } + ], + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", + "time": "2016-04-07 12:29:16" + }, + { + "name": "phpspec/phpspec", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/phpspec.git", + "reference": "53d89ff6d328032c0e434a75af6b0e80ff2d669d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/53d89ff6d328032c0e434a75af6b0e80ff2d669d", + "reference": "53d89ff6d328032c0e434a75af6b0e80ff2d669d", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.1", + "ext-tokenizer": "*", + "php": "^5.6 || ^7.0", + "phpspec/php-diff": "^1.0.0", + "phpspec/prophecy": "^1.5", + "sebastian/exporter": "^1.0", + "symfony/console": "^2.7 || ^3.0", + "symfony/event-dispatcher": "^2.7 || ^3.0", + "symfony/finder": "^2.7 || ^3.0", + "symfony/process": "^2.7 || ^3.0", + "symfony/yaml": "^2.7 || ^3.0" + }, + "require-dev": { + "behat/behat": "^3.1", + "ciaranmcnulty/versionbasedtestskipper": "^0.2.1", + "phpunit/phpunit": "^5.4", + "symfony/filesystem": "^3.0" + }, + "suggest": { + "phpspec/nyan-formatters": "~1.0 – Adds Nyan formatters" + }, + "bin": [ + "bin/phpspec" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "PhpSpec": "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", + "homepage": "http://marcelloduarte.net/" + }, + { + "name": "Ciaran McNulty", + "homepage": "https://ciaranmcnulty.com/" + } + ], + "description": "Specification-oriented BDD framework for PHP 5.6+", + "homepage": "http://phpspec.net/", + "keywords": [ + "BDD", + "SpecBDD", + "TDD", + "spec", + "specification", + "testing", + "tests" + ], + "time": "2016-09-26 21:11:31" + }, + { + "name": "phpspec/prophecy", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "abe41cb27f4e4207c6f54a09272969fe55e0bbff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/abe41cb27f4e4207c6f54a09272969fe55e0bbff", + "reference": "abe41cb27f4e4207c6f54a09272969fe55e0bbff", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "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" + ], + "time": "2017-03-03 17:09:02" + }, + { + "name": "phpunit/php-file-iterator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "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" + ], + "time": "2016-10-03 07:40:28" + }, + { + "name": "phpunit/php-timer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "d107f347d368dd8a384601398280c7c608390ab7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/d107f347d368dd8a384601398280c7c608390ab7", + "reference": "d107f347d368dd8a384601398280c7c608390ab7", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "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" + ], + "time": "2017-03-07 15:42:04" + }, + { + "name": "psr/http-message", + "version": "dev-master", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "psr/log", + "version": "dev-master", + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "sebastian/comparator", + "version": "1.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "18a5d97c25f408f48acaf6d1b9f4079314c5996a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/18a5d97c25f408f48acaf6d1b9f4079314c5996a", + "reference": "18a5d97c25f408f48acaf6d1b9f4079314c5996a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "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" + ], + "time": "2017-03-07 10:34:43" + }, + { + "name": "sebastian/diff", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "763d7adeb8c35d2af2b04c0f6cafeee059074dfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/763d7adeb8c35d2af2b04c0f6cafeee059074dfb", + "reference": "763d7adeb8c35d2af2b04c0f6cafeee059074dfb", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "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" + ], + "time": "2017-03-07 07:26:53" + }, + { + "name": "sebastian/exporter", + "version": "1.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "dcd43bcc0fd3551bd2ede0081882d549bb78225d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dcd43bcc0fd3551bd2ede0081882d549bb78225d", + "reference": "dcd43bcc0fd3551bd2ede0081882d549bb78225d", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "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" + ], + "time": "2017-02-26 13:09:30" + }, + { + "name": "sebastian/finder-facade", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/finder-facade.git", + "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9", + "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9", + "shasum": "" + }, + "require": { + "symfony/finder": "~2.3|~3.0", + "theseer/fdomdocument": "~1.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", + "homepage": "https://github.com/sebastianbergmann/finder-facade", + "time": "2016-02-17 07:02:23" + }, + { + "name": "sebastian/git", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/git.git", + "reference": "815bbbc963cf35e5413df195aa29df58243ecd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/git/zipball/815bbbc963cf35e5413df195aa29df58243ecd24", + "reference": "815bbbc963cf35e5413df195aa29df58243ecd24", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Simple wrapper for Git", + "homepage": "http://www.github.com/sebastianbergmann/git", + "keywords": [ + "git" + ], + "time": "2017-01-23 20:57:12" + }, + { + "name": "sebastian/phpcpd", + "version": "2.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpcpd.git", + "reference": "24d9a880deadb0b8c9680e9cfe78e30b704225db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/24d9a880deadb0b8c9680e9cfe78e30b704225db", + "reference": "24d9a880deadb0b8c9680e9cfe78e30b704225db", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-timer": ">=1.0.6", + "sebastian/finder-facade": "~1.1", + "sebastian/version": "~1.0|~2.0", + "symfony/console": "~2.7|^3.0", + "theseer/fdomdocument": "~1.4" + }, + "bin": [ + "phpcpd" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Copy/Paste Detector (CPD) for PHP code.", + "homepage": "https://github.com/sebastianbergmann/phpcpd", + "time": "2016-04-17 19:32:49" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "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", + "time": "2016-10-03 07:41:43" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "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", + "time": "2015-06-21 13:59:46" + }, + { + "name": "sensiolabs/behat-page-object-extension", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/BehatPageObjectExtension.git", + "reference": "3348a58ecc907597d854d7ed4bbfe8b0eb3af709" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/BehatPageObjectExtension/zipball/3348a58ecc907597d854d7ed4bbfe8b0eb3af709", + "reference": "3348a58ecc907597d854d7ed4bbfe8b0eb3af709", + "shasum": "" + }, + "require": { + "behat/behat": "^3.0.6", + "behat/mink": "^1.6", + "behat/mink-extension": "^2.0", + "ocramius/proxy-manager": "^1.0||^2.0", + "php": ">=5.3.0" + }, + "require-dev": { + "behat/mink-goutte-driver": "^1.0", + "bossa/phpspec2-expect": "^1.0.3||^2.0", + "fabpot/goutte": "^1.0.4||^2.0||^3.0", + "phpspec/phpspec": "^2.5||^3.0", + "symfony/filesystem": "^2.8||^3.0", + "symfony/process": "^2.8||^3.0", + "symfony/yaml": "^2.8||^3.0" + }, + "suggest": { + "bossa/phpspec2-expect": "Allows to use PHPSpec2 matchers in Behat context files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-0": { + "SensioLabs\\Behat\\PageObjectExtension\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marcello Duarte", + "email": "mduarte@inviqa.com" + }, + { + "name": "Jakub Zalas", + "email": "jakub@zalas.pl" + } + ], + "description": "Page object extension for Behat", + "homepage": "https://github.com/sensiolabs/BehatPageObjectExtension", + "keywords": [ + "BDD", + "Behat", + "page" + ], + "time": "2017-02-24 09:42:13" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "1.5.x-dev", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "6f3e42d311b882b25b4d409d23a289f4d3b803d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6f3e42d311b882b25b4d409d23a289f4d3b803d5", + "reference": "6f3e42d311b882b25b4d409d23a289f4d3b803d5", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.1.2" + }, + "suggest": { + "phpunit/php-timer": "dev-master" + }, + "bin": [ + "scripts/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-phpcs-fixer": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/CommentParser/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2014-12-04 22:32:15" + }, + { + "name": "symfony/browser-kit", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "6cc9a89c6b31c4a71bd0f2e2bc608c26b22de5f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6cc9a89c6b31c4a71bd0f2e2bc608c26b22de5f1", + "reference": "6cc9a89c6b31c4a71bd0f2e2bc608c26b22de5f1", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/dom-crawler": "~2.8|~3.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "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 BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2017-03-14 20:25:39" + }, + { + "name": "symfony/class-loader", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/class-loader.git", + "reference": "2c8de07a8a4cc4da9c018ab7a81888b80e762f93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/2c8de07a8a4cc4da9c018ab7a81888b80e762f93", + "reference": "2c8de07a8a4cc4da9c018ab7a81888b80e762f93", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-apcu": "~1.1" + }, + "require-dev": { + "symfony/finder": "^2.0.5|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\ClassLoader\\": "" + }, + "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 ClassLoader Component", + "homepage": "https://symfony.com", + "time": "2017-02-18 19:13:35" + }, + { + "name": "symfony/config", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/06ce6bb46c24963ec09323da45d0f4f85d3cecd2", + "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0" + }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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", + "time": "2017-03-01 18:13:50" + }, + { + "name": "symfony/console", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "3e0b0cb5d669a7d39dd85050910af6ee77e40e7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/3e0b0cb5d669a7d39dd85050910af6ee77e40e7e", + "reference": "3e0b0cb5d669a7d39dd85050910af6ee77e40e7e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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", + "time": "2017-03-27 14:49:15" + }, + { + "name": "symfony/css-selector", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "54f65a2160bb98376455f29f2ddf1de676d4b8ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/54f65a2160bb98376455f29f2ddf1de676d4b8ed", + "reference": "54f65a2160bb98376455f29f2ddf1de676d4b8ed", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2017-02-21 10:07:34" + }, + { + "name": "symfony/debug", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/697c527acd9ea1b2d3efac34d9806bf255278b0a", + "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com", + "time": "2016-07-30 07:22:48" + }, + { + "name": "symfony/dependency-injection", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "a5f3f1265731c33906a725c6f321cb93c2b49f67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a5f3f1265731c33906a725c6f321cb93c2b49f67", + "reference": "a5f3f1265731c33906a725c6f321cb93c2b49f67", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "require-dev": { + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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 DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2017-03-21 21:39:01" + }, + { + "name": "symfony/dom-crawler", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "8e1daf43665f93b0dfc2d47d975e964bf44f018a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8e1daf43665f93b0dfc2d47d975e964bf44f018a", + "reference": "8e1daf43665f93b0dfc2d47d975e964bf44f018a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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 DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2017-02-21 10:07:34" + }, + { + "name": "symfony/event-dispatcher", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "cd88acb5563c690bb02c21b5d7aa801af2520095" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cd88acb5563c690bb02c21b5d7aa801af2520095", + "reference": "cd88acb5563c690bb02c21b5d7aa801af2520095", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-03-21 21:39:01" + }, + { + "name": "symfony/filesystem", + "version": "3.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b2da5009d9bacbd91d83486aa1f44c793a8c380d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b2da5009d9bacbd91d83486aa1f44c793a8c380d", + "reference": "b2da5009d9bacbd91d83486aa1f44c793a8c380d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "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", + "time": "2016-07-20 05:43:46" + }, + { + "name": "symfony/finder", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "851a8764b99bd4173b9992d09ab91050803f0385" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/851a8764b99bd4173b9992d09ab91050803f0385", + "reference": "851a8764b99bd4173b9992d09ab91050803f0385", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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 Finder Component", + "homepage": "https://symfony.com", + "time": "2017-03-20 10:06:58" + }, + { + "name": "symfony/polyfill-apcu", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-apcu.git", + "reference": "5d4474f447403c3348e37b70acc2b95475b7befa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/5d4474f447403c3348e37b70acc2b95475b7befa", + "reference": "5d4474f447403c3348e37b70acc2b95475b7befa", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "apcu", + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/process", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "3b6c34340b6440f8d981ad1741ac5404369a419e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/3b6c34340b6440f8d981ad1741ac5404369a419e", + "reference": "3b6c34340b6440f8d981ad1741ac5404369a419e", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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 Process Component", + "homepage": "https://symfony.com", + "time": "2017-03-27 18:09:43" + }, + { + "name": "symfony/translation", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "047e97a64d609778cadfc76e3a09793696bb19f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/047e97a64d609778cadfc76e3a09793696bb19f1", + "reference": "047e97a64d609778cadfc76e3a09793696bb19f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8", + "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", + "symfony/yaml": "~2.2|~3.0.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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", + "time": "2017-03-21 21:39:01" + }, + { + "name": "symfony/yaml", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "286d84891690b0e2515874717e49360d1c98a703" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/286d84891690b0e2515874717e49360d1c98a703", + "reference": "286d84891690b0e2515874717e49360d1c98a703", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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", + "time": "2017-03-20 09:41:44" + }, + { + "name": "theseer/directoryscanner", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/theseer/DirectoryScanner.git", + "reference": "549aa9fdbc47d50365db42d9ade35fdef65f854c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/DirectoryScanner/zipball/549aa9fdbc47d50365db42d9ade35fdef65f854c", + "reference": "549aa9fdbc47d50365db42d9ade35fdef65f854c", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A recursive directory scanner and filter", + "time": "2015-03-24 21:28:20" + }, + { + "name": "theseer/fdomdocument", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/fDOMDocument.git", + "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", + "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "lib-libxml": "*", + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "lead" + } + ], + "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", + "homepage": "https://github.com/theseer/fDOMDocument", + "time": "2015-05-27 22:58:02" + }, + { + "name": "theseer/fxsl", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/theseer/fXSL.git", + "reference": "a9246376c713156e55c080782d4104bb07d4b899" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/fXSL/zipball/a9246376c713156e55c080782d4104bb07d4b899", + "reference": "a9246376c713156e55c080782d4104bb07d4b899", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xsl": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "An XSL wrapper / extension to the PHP 5.x XSLTProcessor with Exception and extended Callback support", + "time": "2014-11-27 20:08:52" + }, + { + "name": "theseer/phpdox", + "version": "0.6.6.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/phpdox.git", + "reference": "6afa676788d16f2703906082b4a3c9f69772abcc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/phpdox/zipball/6afa676788d16f2703906082b4a3c9f69772abcc", + "reference": "6afa676788d16f2703906082b4a3c9f69772abcc", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-iconv": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xsl": "*", + "nikic/php-parser": ">=1.0.0", + "php": ">=5.3.3", + "phpunit/php-timer": ">=1.0.4", + "theseer/directoryscanner": ">=1.3.0", + "theseer/fdomdocument": ">=1.5.0", + "theseer/fxsl": ">=1.1", + "zetacomponents/base": ">=1.8", + "zetacomponents/console-tools": ">=1.6.0" + }, + "bin": [ + "composer/bin/phpdox" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A fast Documentation generator for PHP Code using standard technology (SRC, DOCBLOCK, XML and XSLT) with event based processing", + "time": "2014-05-04 14:51:11" + }, + { + "name": "webmozart/assert", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "4a8bf11547e139e77b651365113fc12850c43d9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/4a8bf11547e139e77b651365113fc12850c43d9a", + "reference": "4a8bf11547e139e77b651365113fc12850c43d9a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23 20:04:41" + }, + { + "name": "zendframework/zend-code", + "version": "2.6.3", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "95033f061b083e16cdee60530ec260d7d628b887" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/95033f061b083e16cdee60530ec260d7d628b887", + "reference": "95033f061b083e16cdee60530ec260d7d628b887", + "shasum": "" + }, + "require": { + "php": "^5.5 || 7.0.0 - 7.0.4 || ^7.0.6", + "zendframework/zend-eventmanager": "^2.6 || ^3.0" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "^4.8.21", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2016-04-20 17:26:42" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "dev-develop", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "42d2abcdfc56ee2005e9271ac7666d88db7b91b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/42d2abcdfc56ee2005e9271ac7666d88db7b91b9", + "reference": "42d2abcdfc56ee2005e9271ac7666d88db7b91b9", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "phpunit/phpunit": "^5.6", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + }, + "suggest": { + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev", + "dev-develop": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "event", + "eventmanager", + "events", + "zf2" + ], + "time": "2017-03-05 07:56:37" + }, + { + "name": "zetacomponents/base", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/zetacomponents/Base.git", + "reference": "d816bd6d7902d949fae43df135c0ca36bfcf742d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zetacomponents/Base/zipball/d816bd6d7902d949fae43df135c0ca36bfcf742d", + "reference": "d816bd6d7902d949fae43df135c0ca36bfcf742d", + "shasum": "" + }, + "require-dev": { + "zetacomponents/unit-test": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sergey Alexeev" + }, + { + "name": "Sebastian Bergmann" + }, + { + "name": "Jan Borsodi" + }, + { + "name": "Raymond Bosman" + }, + { + "name": "Frederik Holljen" + }, + { + "name": "Kore Nordmann" + }, + { + "name": "Derick Rethans" + }, + { + "name": "Vadym Savchuk" + }, + { + "name": "Tobias Schlitt" + }, + { + "name": "Alexandru Stanoi" + } + ], + "description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.", + "homepage": "https://github.com/zetacomponents", + "time": "2015-06-03 14:25:37" + }, + { + "name": "zetacomponents/console-tools", + "version": "1.6", + "source": { + "type": "git", + "url": "https://github.com/zetacomponents/ConsoleTools.git", + "reference": "e0a0def574009f7cfdf79bf0838a810bcf643775" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zetacomponents/ConsoleTools/zipball/e0a0def574009f7cfdf79bf0838a810bcf643775", + "reference": "e0a0def574009f7cfdf79bf0838a810bcf643775", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "apache2" + ], + "authors": [ + { + "name": "Sergey Alexeev" + }, + { + "name": "Sebastian Bergmann" + }, + { + "name": "Jan Borsodi" + }, + { + "name": "Raymond Bosman" + }, + { + "name": "Frederik Holljen" + }, + { + "name": "Kore Nordmann" + }, + { + "name": "Derick Rethans" + }, + { + "name": "Vadym Savchuk" + }, + { + "name": "Tobias Schlitt" + }, + { + "name": "Alexandru Stanoi" + } + ], + "description": "A set of classes to do different actions with the console (also called shell). It can render a progress bar, tables and a status bar and contains a class for parsing command line options.", + "homepage": "https://github.com/zetacomponents", + "time": "2009-12-21 12:19:33" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "sensiolabs/behat-page-object-extension": 20, + "theseer/fxsl": 20, + "covex-nn/phpcb": 20, + "bex/behat-magento2-init": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~5.5.0|~5.6.0|~7.0.0" + }, + "platform-dev": [] +} diff --git a/magento2/etc/adminhtml/events.xml b/magento2/etc/adminhtml/events.xml new file mode 100644 index 0000000..246bcb2 --- /dev/null +++ b/magento2/etc/adminhtml/events.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/magento2/etc/adminhtml/system.xml b/magento2/etc/adminhtml/system.xml new file mode 100644 index 0000000..1e36630 --- /dev/null +++ b/magento2/etc/adminhtml/system.xml @@ -0,0 +1,57 @@ + + + + +
+ + service + Cloudinary_Cloudinary::config_cloudinary + + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + Set the credentials of your Cloudinary account. Copy the credentials string from the dashboard of Cloudinary's Management Console. It is available as the Environment variable of CLOUDINARY_URL. + Magento\Config\Model\Config\Backend\Encrypted + + + + + + + Enable multiple sub-domains of image delivery URLs for faster page load speed. + Magento\Config\Model\Config\Source\Yesno + + + + + + + Automatically deliver images converted to modern image formats based on viewing device and browser. For example, deliver WebP on Chrome and JPEG-XR on Internet Explorer for better performance and user experience. + Magento\Config\Model\Config\Source\Yesno + + + + Adjust quality of generated images to balance between visual quality and file size minimization. The quality is relevant for JPEG and WebP compression levels for example. + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Quality + + + + Define the part of the image to focus on when cropping images in order to better match your graphic design. + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Gravity + + + + Use DPR value higher than 1.0 to generate and deliver hi-res images for better visual result on HiDPI devices, such as Retina Display devices (e.g., 2.0). + Cloudinary\Cloudinary\Model\Config\Source\Dropdown\Dpr + + +
+
+
diff --git a/magento2/etc/config.xml b/magento2/etc/config.xml new file mode 100644 index 0000000..50461e5 --- /dev/null +++ b/magento2/etc/config.xml @@ -0,0 +1,16 @@ + + + + + + + 1 + 80 + 1.0 + + + 1 + + + + diff --git a/magento2/etc/crontab.xml b/magento2/etc/crontab.xml new file mode 100644 index 0000000..c2bf912 --- /dev/null +++ b/magento2/etc/crontab.xml @@ -0,0 +1,7 @@ + + + + */3 * * * * + + + diff --git a/magento2/etc/di.xml b/magento2/etc/di.xml new file mode 100644 index 0000000..230cb82 --- /dev/null +++ b/magento2/etc/di.xml @@ -0,0 +1,49 @@ + + + + + + + Cloudinary\Cloudinary\Command\UploadImages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/magento2/etc/module.xml b/magento2/etc/module.xml new file mode 100644 index 0000000..a129768 --- /dev/null +++ b/magento2/etc/module.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/magento2/features/bootstrap/FeatureContext.php b/magento2/features/bootstrap/FeatureContext.php new file mode 100644 index 0000000..b0c7b50 --- /dev/null +++ b/magento2/features/bootstrap/FeatureContext.php @@ -0,0 +1,219 @@ +adminLogin = $adminLogin; + $this->productPage = $productPage; + $this->cloudinaryConfig = new CloudinaryConfig(); + $this->cloudinaryManager = new CloudinaryManager(); + $this->productManager = new ProductManager(); + } + + /** + * @Transform :anImage + */ + public function transformStringToAnImage($string) + { + return Image::fromPath($string, self::IMAGE_RELATIVE_PATH); + } + + /** + * @BeforeScenario + */ + public function beforeScenario() + { + $this->removeImageFromMediaFolder(); + $this->product = $this->productManager->createProduct(); + } + + /** + * @AfterScenario + */ + public function afterScenario() + { + $mediaGalleryData = $this->productManager + ->createProduct() + ->getMediaGallery(); + + if (isset($mediaGalleryData['images']) && is_array($mediaGalleryData['images'])) { + foreach ($mediaGalleryData['images'] as &$imageData) { + $imageData['removed'] = 1; + } + $this->product->setData('media_gallery', $mediaGalleryData); + $this->product->save(); + } + + $this->product = null; + $this->uploadException = false; + } + + /** + * @Given I am logged in as an administrator + */ + public function iAmLoggedInAsAnAdministrator() + { + try { + $this->adminLogin->openPage(); + $this->adminLogin->login('admin', 'admin123'); + } catch (\Exception $e) { + + } + } + + /** + * @Given the cloudinary module is disabled + */ + public function theCloudinaryModuleIsDisabled() + { + $this->cloudinaryConfig->disableCloudinary(); + exec('../../../bin/magento ca:cl config'); + } + + /** + * @Given the image :anImage does not exist on the provider + */ + public function theImageDoesNotExistOnTheProvider($anImage) + { + $this->cloudinaryManager->deleteImageFromCloudinary($anImage); + } + + /** + * @When I upload the image :anImage + */ + public function iUploadTheImage(Image $anImage) + { + $imageFilename = (string)$anImage; + + try { + $this->saveProductWithImage($imageFilename); + } catch (MigrationError $e) { + $this->uploadException = $e; + } + + expect(file_exists('../../../pub/media/catalog/product/p/i/'.$imageFilename))->toBe(true); + } + + /** + * @Then the image :anImage will be provided locally + */ + public function theImageWillBeProvidedLocally($anImage) + { + $this->productPage->openPage(['url_key' => $this->product->getUrlKey()]); + + expect($this->productPage)->toNotHaveCloudinaryImageUrl($anImage); + } + + /** + * @Given the cloudinary module is enabled + */ + public function theCloudinaryModuleIsEnabled() + { + $this->cloudinaryConfig->enableCloudinary(); + exec('../../../bin/magento ca:cl config'); + } + + /** + * @Then the image :anImage will be provided remotely + */ + public function theImageWillBeProvidedRemotely($anImage) + { + $this->productPage->openPage(['url_key' => $this->product->getUrlKey()]); + + expect($this->uploadException instanceof MigrationError)->toBe(false); + expect($this->productPage)->toHaveCloudinaryImageUrl($anImage); + } + + /** + * @Given the image :anImage has already been uploaded + */ + public function theImageHasAlreadyBeenUploaded(Image $anImage) + { + Uploader::upload( + __DIR__ . '/../fixtures/images/' . (string)$anImage, + [ + "use_filename" => true, + "unique_filename" => false, + "overwrite" => false, + "folder" => 'catalog/product/p/i/' + ] + ); + } + + /** + * @Then I should see an error image already exists + */ + public function iShouldSeeAnErrorImageAlreadyExists() + { + expect($this->uploadException instanceof MigrationError)->toBe(true); + } + + private function removeImageFromMediaFolder() + { + exec("rm -rf ../../../pub/media/catalog/product/p/i"); + } + + /** + * @param string $imageFilename + */ + private function saveProductWithImage($imageFilename) + { + exec('cp features/fixtures/images/' . $imageFilename . ' /vagrant/pub/media/'); + $this->product->addImageToMediaGallery( + '/vagrant/pub/media/' . $imageFilename, + null, + false, + false + )->save(); + } +} diff --git a/magento2/features/bootstrap/Fixtures/CloudinaryConfig.php b/magento2/features/bootstrap/Fixtures/CloudinaryConfig.php new file mode 100644 index 0000000..4ba304f --- /dev/null +++ b/magento2/features/bootstrap/Fixtures/CloudinaryConfig.php @@ -0,0 +1,30 @@ +configuration = $this->getMagentoObject(Configuration::class); + } + + public function enableCloudinary() + { + $this->configuration->enable(); + } + + public function disableCloudinary() + { + $this->configuration->disable(); + } +} diff --git a/magento2/features/bootstrap/Fixtures/CloudinaryManager.php b/magento2/features/bootstrap/Fixtures/CloudinaryManager.php new file mode 100644 index 0000000..cd06681 --- /dev/null +++ b/magento2/features/bootstrap/Fixtures/CloudinaryManager.php @@ -0,0 +1,25 @@ +cloudinaryImageProvider = CloudinaryImageProvider::fromConfiguration( + $this->getMagentoObject(Configuration::class) + ); + } + + public function deleteImageFromCloudinary($image) + { + $this->cloudinaryImageProvider->delete($image); + } +} diff --git a/magento2/features/bootstrap/Fixtures/ProductManager.php b/magento2/features/bootstrap/Fixtures/ProductManager.php new file mode 100644 index 0000000..b9d2051 --- /dev/null +++ b/magento2/features/bootstrap/Fixtures/ProductManager.php @@ -0,0 +1,19 @@ +createMagentoObject(ProductInterface::class); + return $testproduct->load(self::TEST_PRODUCT_ID); + } +} diff --git a/magento2/features/bootstrap/Helpers/PageObjectHelperMethods.php b/magento2/features/bootstrap/Helpers/PageObjectHelperMethods.php new file mode 100644 index 0000000..6011e8a --- /dev/null +++ b/magento2/features/bootstrap/Helpers/PageObjectHelperMethods.php @@ -0,0 +1,146 @@ +open($params); + $this->waitForPageLoad(); + } + + function acceptAlert() + { + $this->getDriver()->getWebDriverSession()->accept_alert(); + } + + /** + * @param mixed $condition + * @param int $maxWait + */ + function waitForCondition($condition, $maxWait = 120000) + { + $this->getSession()->wait($maxWait, $condition); + } + + /** + * @param int $maxWait + */ + function waitForPageLoad($maxWait = 120000) + { + $this->waitForCondition('(document.readyState == "complete") && (typeof window.jQuery == "function") && (jQuery.active == 0)', $maxWait); + } + + /** + * @param string $elementName + * @param int $maxWait + */ + function waitForElement($elementName, $maxWait = 120000) + { + $visibilityCheck = $this->getElementVisibilyCheck($elementName); + $this->waitForCondition("(typeof window.jQuery == 'function') && $visibilityCheck", $maxWait); + } + + /** + * @param string $elementName + * @param int $maxWait + */ + function waitUntilElementDisappear($elementName, $maxWait = 120000) + { + $visibilityCheck = $this->getElementVisibilyCheck($elementName); + $this->waitForCondition("(typeof window.jQuery == 'function') && !$visibilityCheck", $maxWait); + } + + /** + * @param int $waitTime + */ + function waitTime($waitTime) + { + $this->getSession()->wait($waitTime); + } + + function scrollToBottom() + { + $this->getSession()->executeScript('window.scrollTo(0,document.body.scrollHeight);'); + } + + /** + * @param string $elementName + */ + function clickElement($elementName) + { + $this->getElementWithWait($elementName)->click(); + } + + /** + * @param string $elementName + * @return mixed + */ + function getElementValue($elementName) + { + return $this->getElementWithWait($elementName)->getValue(); + } + + /** + * @param string $elementName + * @param string $value + */ + function setElementValue($elementName, $value) + { + $this->getElementWithWait($elementName)->setValue($value); + } + + /** + * @param string $elementName + */ + function getElementText($elementName) + { + return $this->getElementWithWait($elementName)->getText(); + } + + /** + * @param string $elementName + * @param int $waitTime + * @return mixed + */ + public function getElementWithWait($elementName, $waitTime = 2500) + { + $this->waitForElement($elementName, $waitTime); + return $this->getElement($elementName); + } + + /** + * @param $elementName + * @return string + */ + public function getElementVisibilyCheck($elementName) + { + $visibilityCheck = 'true'; + + if (isset($this->elements[$elementName]['css'])) { + $elementFinder = $this->elements[$elementName]['css']; + $visibilityCheck = "jQuery('$elementFinder').is(':visible')"; + } + + if (isset($this->elements[$elementName]['xpath'])) { + $elementFinder = $this->elements[$elementName]['xpath']; + $visibilityCheck = "jQuery(document.evaluate('$elementFinder', document, null, XPathResult.ANY_TYPE, null).iterateNext()).is(':visible')"; + } + + return $visibilityCheck; + } + + /** + * @param string $elementName + * @return mixed + */ + public function isElementVisible($elementName) + { + $xpath = $this->getElement($elementName)->getXpath(); + return $this->getDriver()->isVisible($xpath); + } +} diff --git a/magento2/features/bootstrap/Page/Admin/Login.php b/magento2/features/bootstrap/Page/Admin/Login.php new file mode 100644 index 0000000..a4d2053 --- /dev/null +++ b/magento2/features/bootstrap/Page/Admin/Login.php @@ -0,0 +1,27 @@ + ['css' => '#username'], + 'Password' => ['css' => '#login'], + 'Login button' => ['css' => '.action-login'] + ]; + + public function login($username, $password) + { + $this->setElementValue('Username', $username); + $this->setElementValue('Password', $password); + $this->clickElement('Login button'); + $this->waitForPageLoad(); + } +} diff --git a/magento2/features/bootstrap/Page/Product.php b/magento2/features/bootstrap/Page/Product.php new file mode 100644 index 0000000..2c6fbd1 --- /dev/null +++ b/magento2/features/bootstrap/Page/Product.php @@ -0,0 +1,29 @@ + ['css' => '.fotorama__img'] + ]; + + function getMainImageUrl() + { + return $this->getElementWithWait('Main Image')->getAttribute('src'); + } + + function hasCloudinaryImageUrl(Image $image) + { + return (strpos($this->getMainImageUrl(), 'cloudinary.com') !== false) + && (strpos($this->getMainImageUrl(), $image->getId()) !== false); + } +} diff --git a/magento2/features/fixtures/images/pink_dress.gif b/magento2/features/fixtures/images/pink_dress.gif new file mode 100644 index 0000000..04dc579 Binary files /dev/null and b/magento2/features/fixtures/images/pink_dress.gif differ diff --git a/magento2/registration.php b/magento2/registration.php new file mode 100644 index 0000000..422034d --- /dev/null +++ b/magento2/registration.php @@ -0,0 +1,7 @@ +beConstructedWith( + $imageRepository, + $configuration, + $migrationTask, + $cloudinaryImageManager + ); + } + + function it_should_not_do_batch_upload_when_migration_task_started( + OutputInterface $outputInterface, + MigrationTask $migrationTask + ) { + $migrationTask->hasStarted()->willReturn(true); + + $this->uploadUnsynchronisedImages($outputInterface)->shouldBe(false); + } + + function it_should_upload_and_synchronised_images( + OutputInterface $outputInterface, + MigrationTask $migrationTask, + ImageRepository $imageRepository, + Image $image + ) + { + $image->__toString()->willReturn('pink_image.gif'); + $image->getRelativePath()->willReturn('/p/i/pink_dress.gif'); + + $migrationTask->hasStarted()->willReturn(false); + + $migrationTask->start()->shouldBeCalled(); + $migrationTask->stop()->shouldBeCalled(); + + $imageRepository->findUnsynchronisedImages()->willReturn([$image]); + + $this->uploadUnsynchronisedImages($outputInterface)->shouldBe(true); + } +} diff --git a/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/DeletedImageFilterSpec.php b/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/DeletedImageFilterSpec.php new file mode 100644 index 0000000..dca0f9f --- /dev/null +++ b/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/DeletedImageFilterSpec.php @@ -0,0 +1,26 @@ +__invoke($imageData)->shouldReturn(false); + } + + function it_should_return_empty_when_no_removed_images() + { + $imageData = ['removed' => 0]; + $this->__invoke($imageData)->shouldReturn(false); + } + + function it_should_return_images_marked_as_removed() + { + $imageData = ['removed' => 1]; + $this->__invoke($imageData)->shouldReturn(true); + } +} \ No newline at end of file diff --git a/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/ImageCreatorSpec.php b/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/ImageCreatorSpec.php new file mode 100644 index 0000000..01c3f74 --- /dev/null +++ b/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/ImageCreatorSpec.php @@ -0,0 +1,35 @@ +beConstructedWith($filesystem, $mediaConfig); + } + + function it_should_return_instance_of_an_image( + Filesystem $filesystem, + ReadInterface $mediaDirectory + ) { + + $filesystem->getDirectoryRead(DirectoryList::MEDIA) + ->shouldBeCalled() + ->willReturn($mediaDirectory); + + $imageData = ['file' => '/p/i/pink_dress.gif']; + + $this->__invoke($imageData)->shouldBeAnInstanceOf('CloudinaryExtension\Image'); + } +} \ No newline at end of file diff --git a/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/NewImageFilterSpec.php b/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/NewImageFilterSpec.php new file mode 100644 index 0000000..e6c98fa --- /dev/null +++ b/magento2/spec/Cloudinary/Cloudinary/Model/ProductImageFinder/NewImageFilterSpec.php @@ -0,0 +1,20 @@ +__invoke($imageData)->shouldReturn(false); + } + + function it_should_return_images_marked_as_new() + { + $imageData = ['new_file' => 1]; + $this->__invoke($imageData)->shouldReturn(true); + } +} \ No newline at end of file diff --git a/magento2/spec/Cloudinary/Cloudinary/Model/SynchronisationCheckerSpec.php b/magento2/spec/Cloudinary/Cloudinary/Model/SynchronisationCheckerSpec.php new file mode 100644 index 0000000..be9945e --- /dev/null +++ b/magento2/spec/Cloudinary/Cloudinary/Model/SynchronisationCheckerSpec.php @@ -0,0 +1,52 @@ +beConstructedWith($synchronisationRepository); + } + + function it_validates_not_synchronized_for_null_image_name() + { + $imageName = ''; + $this->isSynchronized($imageName)->shouldBe(false); + } + + function it_validates_not_synchronized_if_collection_for_given_image_name_is_empty( + SynchronisationRepository $synchronisationRepository, + SearchResults $searchResults + ) + { + $imageName = 'pink_dress.gif'; + $searchResults->getTotalCount()->willReturn(0); + + $synchronisationRepository->getListByImagePath($imageName) + ->shouldBeCalled() + ->willReturn($searchResults); + + $this->isSynchronized($imageName)->shouldBe(false); + } + + function it_validates_synchronized_for_a_valid_image_name( + SynchronisationRepository $synchronisationRepository, + SearchResults $searchResults + ) + { + $imageName = 'pink_dress.gif'; + $searchResults->getTotalCount()->willReturn(1); + + $synchronisationRepository->getListByImagePath($imageName) + ->shouldBeCalled() + ->willReturn($searchResults); + + $this->isSynchronized($imageName)->shouldBe(true); + } +} \ No newline at end of file diff --git a/magento2/spec/Cloudinary/Cloudinary/Plugin/FileUploaderSpec.php b/magento2/spec/Cloudinary/Cloudinary/Plugin/FileUploaderSpec.php new file mode 100644 index 0000000..6b7baa7 --- /dev/null +++ b/magento2/spec/Cloudinary/Cloudinary/Plugin/FileUploaderSpec.php @@ -0,0 +1,52 @@ +getPath('media')->willReturn('/var/app/media'); + + $this->beConstructedWith( + $cloudinaryImageManager, + $directoryList + ); + } + + function it_is_initializable() + { + $this->shouldHaveType(FileUploader::class); + } + + function it_uploads_wysiwyg_file( + Uploader $uploader, + CloudinaryImageManager $cloudinaryImageManager + ) { + $image = Image::fromPath('/var/app/media/wysiwyg/foo.jpg', 'wysiwyg/foo.jpg'); + + $cloudinaryImageManager->uploadAndSynchronise($image)->shouldBeCalled(); + + $this->afterSave($uploader, ['path' => '/var/app/media/wysiwyg', 'file' => 'foo.jpg']); + } + + function it_does_not_upload_tmp_file( + Uploader $uploader, + CloudinaryImageManager $cloudinaryImageManager + ) { + $image = Image::fromPath('/var/app/media/tmp/foo.jpg', 'tmp/foo.jpg'); + + $cloudinaryImageManager->uploadAndSynchronise($image)->shouldNotBeCalled(); + + $this->afterSave($uploader, ['path' => '/var/app/media/tmp', 'file' => 'foo.jpg']); + } +} diff --git a/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php b/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php deleted file mode 100755 index 841ee47..0000000 --- a/src/app/code/community/Cloudinary/Cloudinary/Model/SynchronisedMediaUnifier.php +++ /dev/null @@ -1,27 +0,0 @@ -_synchronisedMediaRepositories = $synchronisedMediaRepositories; - } - - public function findUnsynchronisedImages($limit = 200) - { - foreach ($this->_synchronisedMediaRepositories as $synchronisedMediaRepository) { - $this->_unsychronisedImages = array_merge( - $this->_unsychronisedImages, - $synchronisedMediaRepository->findUnsynchronisedImages() - ); - } - return array_slice($this->_unsychronisedImages, 0, $limit); - } - -}