mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-23 18:03:17 +00:00
Merge pull request #368 from romainneutron/plugins
[3.8] Welcome Plugins
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@
|
||||
/config/connexions.yml
|
||||
.DS_Store
|
||||
/vendor
|
||||
/plugins
|
||||
composer.phar
|
||||
behat.yml
|
||||
/datas
|
||||
|
@@ -27,6 +27,7 @@
|
||||
- Add bin/developer console for developement purpose.
|
||||
- Add possibility to delete a basket from the workzone basket browser.
|
||||
- Add localized labels for databox documentary fields.
|
||||
- Add plugin architecture for third party modules and customization.
|
||||
|
||||
* 3.7.12 (2013-05-13)
|
||||
|
||||
|
@@ -24,8 +24,10 @@ use Alchemy\Phrasea\Command\RecordAdd;
|
||||
use Alchemy\Phrasea\Command\RescanTechnicalDatas;
|
||||
use Alchemy\Phrasea\Command\UpgradeDBDatas;
|
||||
use Alchemy\Phrasea\CLI;
|
||||
use Alchemy\Phrasea\Command\Plugin\AddPlugin;
|
||||
use Alchemy\Phrasea\Command\Plugin\RemovePlugin;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../lib/autoload.php';
|
||||
|
||||
try {
|
||||
$app = new CLI("
|
||||
@@ -90,6 +92,9 @@ try {
|
||||
$app->command(new RescanTechnicalDatas('records:rescan-technical-datas'));
|
||||
$app->command(new BuildMissingSubdefs('records:build-missing-subdefs'));
|
||||
|
||||
$app->command(new AddPlugin());
|
||||
$app->command(new RemovePlugin());
|
||||
|
||||
$result_code = is_int($app->run()) ? : 1;
|
||||
} catch (\Exception $e) {
|
||||
$result_code = 1;
|
||||
|
@@ -36,7 +36,7 @@ use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
|
||||
use Doctrine\ORM\Tools\Console\Command\RunDqlCommand;
|
||||
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../lib/autoload.php';
|
||||
|
||||
try {
|
||||
$cli = new CLI("
|
||||
|
@@ -16,8 +16,10 @@
|
||||
"facebook/php-sdk" : "~3.0",
|
||||
"gedmo/doctrine-extensions" : "~2.3.0",
|
||||
"google-plus/api-client" : "~0.6.1",
|
||||
"guzzle/guzzle" : "~3.0",
|
||||
"imagine/imagine" : "dev-ColorSpaces@dev",
|
||||
"jms/serializer" : "~0.10",
|
||||
"justinrainbow/json-schema" : "~1.3",
|
||||
"mediavorus/mediavorus" : "~0.3",
|
||||
"media-alchemyst/media-alchemyst" : "~0.2.3",
|
||||
"monolog/monolog" : "~1.3.0",
|
||||
@@ -27,6 +29,7 @@
|
||||
"neutron/silex-filesystem-provider": "dev-master",
|
||||
"neutron/sphinxsearch-api" : "~2.0.6",
|
||||
"neutron/recaptcha" : "~0.1.0",
|
||||
"neutron/temporary-filesystem" : "~2.0",
|
||||
"php-xpdf/php-xpdf" : "~0.1.1",
|
||||
"phpexiftool/phpexiftool" : "~0.2, >=0.2.2",
|
||||
"silex/silex" : "~1.0.0",
|
||||
|
158
composer.lock
generated
158
composer.lock
generated
@@ -3,7 +3,7 @@
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
|
||||
],
|
||||
"hash": "7fd45681e93a7fe1d0f4e691818ad4e2",
|
||||
"hash": "06eddf18808e8091de82ec3b5d6b699e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "BadFaith/BadFaith",
|
||||
@@ -1091,6 +1091,64 @@
|
||||
],
|
||||
"time": "2013-03-28 16:41:24"
|
||||
},
|
||||
{
|
||||
"name": "justinrainbow/json-schema",
|
||||
"version": "1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/justinrainbow/json-schema.git",
|
||||
"reference": "1.3.1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/1.3.1",
|
||||
"reference": "1.3.1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/validate-json"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"JsonSchema": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch",
|
||||
"homepage": "http://wiedler.ch/igor/"
|
||||
},
|
||||
{
|
||||
"name": "Bruno Prieto Reis",
|
||||
"email": "bruno.p.reis@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Justin Rainbow",
|
||||
"email": "justin.rainbow@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Robert Schönthal",
|
||||
"email": "seroscho@googlemail.com",
|
||||
"homepage": "http://digitalkaoz.net"
|
||||
}
|
||||
],
|
||||
"description": "A library to validate a json schema.",
|
||||
"homepage": "https://github.com/justinrainbow/json-schema",
|
||||
"keywords": [
|
||||
"json",
|
||||
"schema"
|
||||
],
|
||||
"time": "2013-02-21 04:49:21"
|
||||
},
|
||||
{
|
||||
"name": "media-alchemyst/media-alchemyst",
|
||||
"version": "0.2.5",
|
||||
@@ -1540,6 +1598,43 @@
|
||||
],
|
||||
"time": "2012-10-23 02:05:12"
|
||||
},
|
||||
{
|
||||
"name": "neutron/temporary-filesystem",
|
||||
"version": "2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/romainneutron/Temporary-Filesystem.git",
|
||||
"reference": "2.0.1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/romainneutron/Temporary-Filesystem/zipball/2.0.1",
|
||||
"reference": "2.0.1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"symfony/filesystem": ">=2.0,<3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Neutron": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Romain Neutron",
|
||||
"email": "imprec@gmail.com",
|
||||
"homepage": "http://www.lickmychip.com/"
|
||||
}
|
||||
],
|
||||
"description": "Symfony filesystem extension to handle temporary files",
|
||||
"time": "2013-04-09 08:38:21"
|
||||
},
|
||||
{
|
||||
"name": "php-ffmpeg/php-ffmpeg",
|
||||
"version": "0.2.4",
|
||||
@@ -2722,21 +2817,21 @@
|
||||
},
|
||||
{
|
||||
"name": "behat/mink",
|
||||
"version": "v1.4.3",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/Mink.git",
|
||||
"reference": "v1.4.3"
|
||||
"reference": "v1.5.0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/Mink/zipball/v1.4.3",
|
||||
"reference": "v1.4.3",
|
||||
"url": "https://api.github.com/repos/Behat/Mink/zipball/v1.5.0",
|
||||
"reference": "v1.5.0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.1",
|
||||
"symfony/css-selector": ">=2.0,<2.4-dev"
|
||||
"symfony/css-selector": ">=2.0,<3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
|
||||
@@ -2747,7 +2842,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-develop": "1.4.x-dev"
|
||||
"dev-develop": "1.5.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -2773,24 +2868,24 @@
|
||||
"testing",
|
||||
"web"
|
||||
],
|
||||
"time": "2013-03-02 15:53:18"
|
||||
"time": "2013-04-13 23:39:27"
|
||||
},
|
||||
{
|
||||
"name": "behat/mink-browserkit-driver",
|
||||
"version": "v1.0.5",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/MinkBrowserKitDriver.git",
|
||||
"reference": "v1.0.5"
|
||||
"reference": "v1.1.0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/MinkBrowserKitDriver/zipball/v1.0.5",
|
||||
"reference": "v1.0.5",
|
||||
"url": "https://api.github.com/repos/Behat/MinkBrowserKitDriver/zipball/v1.1.0",
|
||||
"reference": "v1.1.0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"behat/mink": ">=1.4.3,<1.5",
|
||||
"behat/mink": ">=1.5,<1.6",
|
||||
"php": ">=5.3.1",
|
||||
"symfony/browser-kit": ">=2.0,<3.0",
|
||||
"symfony/dom-crawler": ">=2.0,<3.0"
|
||||
@@ -2801,7 +2896,7 @@
|
||||
"type": "mink-driver",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -2828,30 +2923,29 @@
|
||||
"browser",
|
||||
"testing"
|
||||
],
|
||||
"time": "2013-04-13 12:17:15"
|
||||
"time": "2013-04-13 23:46:30"
|
||||
},
|
||||
{
|
||||
"name": "behat/mink-extension",
|
||||
"version": "v1.0.1",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/MinkExtension.git",
|
||||
"reference": "v1.0.1"
|
||||
"reference": "v1.1.0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/MinkExtension/zipball/v1.0.1",
|
||||
"reference": "v1.0.1",
|
||||
"url": "https://api.github.com/repos/Behat/MinkExtension/zipball/v1.1.0",
|
||||
"reference": "v1.1.0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"behat/behat": ">=2.4,<2.5-dev",
|
||||
"behat/mink": ">=1.4,<1.5-dev",
|
||||
"behat/behat": ">=2.4.5,<2.5",
|
||||
"behat/mink": ">=1.4.3,<1.6-dev",
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"behat/mink-goutte-driver": "1.0.*",
|
||||
"fabpot/goutte": "1.0.*@dev"
|
||||
"behat/mink-goutte-driver": ">=1.0,<2.0"
|
||||
},
|
||||
"type": "behat-extension",
|
||||
"extra": {
|
||||
@@ -2883,7 +2977,7 @@
|
||||
"test",
|
||||
"web"
|
||||
],
|
||||
"time": "2013-02-19 23:49:16"
|
||||
"time": "2013-05-23 12:32:57"
|
||||
},
|
||||
{
|
||||
"name": "behat/mink-goutte-driver",
|
||||
@@ -2938,27 +3032,27 @@
|
||||
},
|
||||
{
|
||||
"name": "behat/mink-selenium2-driver",
|
||||
"version": "v1.0.6",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/MinkSelenium2Driver.git",
|
||||
"reference": "v1.0.6"
|
||||
"reference": "v1.1.0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/MinkSelenium2Driver/zipball/v1.0.6",
|
||||
"reference": "v1.0.6",
|
||||
"url": "https://api.github.com/repos/Behat/MinkSelenium2Driver/zipball/v1.1.0",
|
||||
"reference": "v1.1.0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"behat/mink": ">=1.4.3,<1.5",
|
||||
"behat/mink": ">=1.5,<1.6",
|
||||
"instaclick/php-webdriver": ">=1.0.12.0,<1.1",
|
||||
"php": ">=5.3.1"
|
||||
},
|
||||
"type": "mink-driver",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -2992,7 +3086,7 @@
|
||||
"testing",
|
||||
"webdriver"
|
||||
],
|
||||
"time": "2013-04-13 12:56:28"
|
||||
"time": "2013-04-13 23:51:01"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/data-fixtures",
|
||||
@@ -3037,7 +3131,7 @@
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jonathan H. Wage",
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com",
|
||||
"homepage": "http://www.jwage.com/"
|
||||
}
|
||||
|
@@ -89,9 +89,11 @@ use Alchemy\Phrasea\Core\Provider\NotificationDelivererServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\ORMServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\PhraseanetServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\PhraseaVersionServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\PluginServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\RegistrationServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\SearchEngineServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\TaskManagerServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\TemporaryFilesystemServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\TokensServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\UnicodeServiceProvider;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
@@ -241,6 +243,7 @@ class Application extends SilexApplication
|
||||
$this->register(new InstallerServiceProvider());
|
||||
$this->register(new PhraseanetServiceProvider());
|
||||
$this->register(new PhraseaVersionServiceProvider());
|
||||
$this->register(new PluginServiceProvider());
|
||||
$this->register(new PHPExiftoolServiceProvider());
|
||||
$this->register(new ReCaptchaServiceProvider());
|
||||
|
||||
@@ -262,6 +265,7 @@ class Application extends SilexApplication
|
||||
$this->register(new ServiceControllerServiceProvider());
|
||||
$this->register(new SwiftmailerServiceProvider());
|
||||
$this->register(new TaskManagerServiceProvider());
|
||||
$this->register(new TemporaryFilesystemServiceProvider());
|
||||
$this->register(new TokensServiceProvider());
|
||||
$this->register(new TwigServiceProvider(), array(
|
||||
'twig.options' => array(
|
||||
@@ -397,6 +401,10 @@ class Application extends SilexApplication
|
||||
|
||||
return $data[1];
|
||||
};
|
||||
|
||||
call_user_func(function ($app) {
|
||||
require $app['plugins.directory'] . '/services.php';
|
||||
}, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
41
lib/Alchemy/Phrasea/Command/Plugin/AbstractPluginCommand.php
Normal file
41
lib/Alchemy/Phrasea/Command/Plugin/AbstractPluginCommand.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Command\Plugin;
|
||||
|
||||
use Alchemy\Phrasea\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
abstract class AbstractPluginCommand extends Command
|
||||
{
|
||||
protected function validatePlugins(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$manifests = array();
|
||||
|
||||
$output->write("Validating plugins...");
|
||||
foreach ($this->container['plugins.explorer'] as $directory) {
|
||||
$manifests[] = $manifest = $this->container['plugins.plugins-validator']->validatePlugin($directory);
|
||||
}
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
|
||||
return $manifests;
|
||||
}
|
||||
|
||||
protected function updateConfigFiles(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$manifests = $this->validatePlugins($input, $output);
|
||||
|
||||
$output->write("Updating config files...");
|
||||
$this->container['plugins.autoloader-generator']->write($manifests);
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
}
|
||||
}
|
61
lib/Alchemy/Phrasea/Command/Plugin/AddPlugin.php
Normal file
61
lib/Alchemy/Phrasea/Command/Plugin/AddPlugin.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Command\Plugin;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class AddPlugin extends AbstractPluginCommand
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('plugins:add');
|
||||
|
||||
$this
|
||||
->setDescription('Installs a plugin to Phraseanet')
|
||||
->addArgument('source', InputArgument::REQUIRED, 'The source is a folder');
|
||||
}
|
||||
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$source = $input->getArgument('source');
|
||||
|
||||
$temporaryDir = $this->container['temporary-filesystem']->createTemporaryDirectory();
|
||||
|
||||
$output->write("Importing <info>$source</info>...");
|
||||
$this->container['plugins.importer']->import($source, $temporaryDir);
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
|
||||
$output->write("Validating plugin...");
|
||||
$manifest = $this->container['plugins.plugins-validator']->validatePlugin($temporaryDir);
|
||||
$output->writeln(" <comment>OK</comment> found <info>".$manifest->getName()."</info>");
|
||||
|
||||
$targetDir = $this->container['plugins.directory'] . DIRECTORY_SEPARATOR . $manifest->getName();
|
||||
|
||||
$output->write("Setting up composer...");
|
||||
$this->container['plugins.composer-installer']->install($temporaryDir);
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
|
||||
$output->write("Installing plugin <info>".$manifest->getName()."</info>...");
|
||||
$this->container['filesystem']->mirror($temporaryDir, $targetDir);
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
|
||||
$output->write("Removing temporary directory...");
|
||||
$this->container['filesystem']->remove($temporaryDir);
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
|
||||
$this->updateConfigFiles($input, $output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
43
lib/Alchemy/Phrasea/Command/Plugin/RemovePlugin.php
Normal file
43
lib/Alchemy/Phrasea/Command/Plugin/RemovePlugin.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Command\Plugin;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class RemovePlugin extends AbstractPluginCommand
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('plugins:remove');
|
||||
|
||||
$this
|
||||
->setDescription('Removes a plugin given its name')
|
||||
->addArgument('name', InputArgument::REQUIRED, 'The name of the plugin');
|
||||
}
|
||||
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$name = $input->getArgument('name');
|
||||
|
||||
$path = $this->container['plugins.directory'] . DIRECTORY_SEPARATOR . $name;
|
||||
|
||||
$output->write("Removing <info>$name</info>...");
|
||||
$this->container['filesystem']->remove($path);
|
||||
$output->writeln(" <comment>OK</comment>");
|
||||
|
||||
$this->updateConfigFiles($input, $output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
86
lib/Alchemy/Phrasea/Core/Provider/PluginServiceProvider.php
Normal file
86
lib/Alchemy/Phrasea/Core/Provider/PluginServiceProvider.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Provider;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
|
||||
use Alchemy\Phrasea\Plugin\Management\PluginsExplorer;
|
||||
use Alchemy\Phrasea\Plugin\Management\ComposerInstaller;
|
||||
use Alchemy\Phrasea\Plugin\Schema\PluginValidator;
|
||||
use Alchemy\Phrasea\Plugin\Importer\Importer;
|
||||
use Alchemy\Phrasea\Plugin\Importer\ImportStrategy;
|
||||
use Alchemy\Phrasea\Plugin\Importer\FolderImporter;
|
||||
use Alchemy\Phrasea\Plugin\Management\AutoloaderGenerator;
|
||||
use Guzzle\Http\Client as Guzzle;
|
||||
use JsonSchema\Validator as JsonValidator;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
use Silex\Application;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
class PluginServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugins.directory'] = realpath(__DIR__ . '/../../../../../plugins');
|
||||
$app['plugins.schema'] = realpath(__DIR__ . '/../../../../conf.d/plugin-schema.json');
|
||||
|
||||
$app['plugins.json-validator'] = $app->share(function (Application $app) {
|
||||
return new JsonValidator();
|
||||
});
|
||||
|
||||
$app['plugins.manifest-validator'] = $app->share(function (Application $app) {
|
||||
return ManifestValidator::create($app);
|
||||
});
|
||||
|
||||
$app['plugins.plugins-validator'] = $app->share(function (Application $app) {
|
||||
return new PluginValidator($app['plugins.manifest-validator']);
|
||||
});
|
||||
|
||||
$app['plugins.import-strategy'] = $app->share(function (Application $app) {
|
||||
return new ImportStrategy();
|
||||
});
|
||||
|
||||
$app['plugins.autoloader-generator'] = $app->share(function (Application $app) {
|
||||
return new AutoloaderGenerator($app['plugins.directory']);
|
||||
});
|
||||
|
||||
$app['plugins.guzzle'] = $app->share(function (Application $app) {
|
||||
return new Guzzle();
|
||||
});
|
||||
|
||||
$app['plugins.composer-installer'] = $app->share(function (Application $app) {
|
||||
$phpBinary = $app['phraseanet.registry']->get('php_binary');
|
||||
if (!is_executable($phpBinary)) {
|
||||
$finder = new ExecutableFinder();
|
||||
$phpBinary = $finder->find('php');
|
||||
}
|
||||
|
||||
return new ComposerInstaller($app['plugins.directory'], $app['plugins.guzzle'], $phpBinary);
|
||||
});
|
||||
$app['plugins.explorer'] = $app->share(function (Application $app) {
|
||||
return new PluginsExplorer($app['plugins.directory']);
|
||||
});
|
||||
|
||||
$app['plugins.importer'] = $app->share(function (Application $app) {
|
||||
return new Importer($app['plugins.import-strategy'], array(
|
||||
'plugins.importer.folder-importer' => $app['plugins.importer.folder-importer'],
|
||||
));
|
||||
});
|
||||
|
||||
$app['plugins.importer.folder-importer'] = $app->share(function (Application $app) {
|
||||
return new FolderImporter($app['filesystem']);
|
||||
});
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Provider;
|
||||
|
||||
use Neutron\TemporaryFilesystem\TemporaryFilesystem;
|
||||
use Silex\Application;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
class TemporaryFilesystemServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['temporary-filesystem'] = $app->share(function (Application $app) {
|
||||
return new TemporaryFilesystem($app['filesystem']);
|
||||
});
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Exception;
|
||||
|
||||
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||
|
||||
class ComposerInstallException extends RuntimeException
|
||||
{
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Exception;
|
||||
|
||||
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||
|
||||
class ImportFailureException extends RuntimeException
|
||||
{
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Exception;
|
||||
|
||||
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||
|
||||
class JsonValidationException extends RuntimeException
|
||||
{
|
||||
protected $errors;
|
||||
|
||||
public function __construct($message, $errors = array())
|
||||
{
|
||||
$this->errors = $errors;
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Exception;
|
||||
|
||||
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||
|
||||
class PluginValidationException extends RuntimeException
|
||||
{
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Exception;
|
||||
|
||||
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||
|
||||
class RegistrationFailureException extends RuntimeException
|
||||
{
|
||||
}
|
44
lib/Alchemy/Phrasea/Plugin/Importer/FolderImporter.php
Normal file
44
lib/Alchemy/Phrasea/Plugin/Importer/FolderImporter.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Exception\ImportFailureException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Filesystem\Exception\ExceptionInterface as FsException;
|
||||
|
||||
class FolderImporter implements ImporterInterface
|
||||
{
|
||||
private $fs;
|
||||
|
||||
public function __construct(Filesystem $fs)
|
||||
{
|
||||
$this->fs = $fs;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import($source, $target)
|
||||
{
|
||||
try {
|
||||
$this->fs->mirror($source, $target);
|
||||
} catch (FsException $e) {
|
||||
try {
|
||||
$this->fs->remove($target);
|
||||
} catch (FsException $e) {
|
||||
|
||||
}
|
||||
|
||||
throw new ImportFailureException(sprintf('Unable to import from `%s` to `%s`', $source, $target), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
27
lib/Alchemy/Phrasea/Plugin/Importer/ImportStrategy.php
Normal file
27
lib/Alchemy/Phrasea/Plugin/Importer/ImportStrategy.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Exception\ImportFailureException;
|
||||
|
||||
class ImportStrategy
|
||||
{
|
||||
public function detect($source)
|
||||
{
|
||||
switch (true) {
|
||||
case file_exists($source) && is_dir($source):
|
||||
return 'plugins.importer.folder-importer';
|
||||
default:
|
||||
throw new ImportFailureException(sprintf('Unable to detect source type for `%s`', $source));
|
||||
}
|
||||
}
|
||||
}
|
44
lib/Alchemy/Phrasea/Plugin/Importer/Importer.php
Normal file
44
lib/Alchemy/Phrasea/Plugin/Importer/Importer.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Exception\ImportFailureException;
|
||||
|
||||
class Importer
|
||||
{
|
||||
private $importers;
|
||||
private $strategy;
|
||||
|
||||
public function __construct(ImportStrategy $strategy, $importers)
|
||||
{
|
||||
$this->importers = $importers;
|
||||
$this->strategy = $strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
*
|
||||
* @throws ImportFailureException
|
||||
*/
|
||||
public function import($source, $target)
|
||||
{
|
||||
$strategy = $this->strategy->detect($source);
|
||||
|
||||
if (!isset($this->importers[$strategy])) {
|
||||
throw new ImportFailureException(sprintf('Unable to get an import for source `%s`', $source));
|
||||
}
|
||||
|
||||
$this->importers[$strategy]->import($source, $target);
|
||||
}
|
||||
}
|
25
lib/Alchemy/Phrasea/Plugin/Importer/ImporterInterface.php
Normal file
25
lib/Alchemy/Phrasea/Plugin/Importer/ImporterInterface.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Exception\ImportFailureException;
|
||||
|
||||
interface ImporterInterface
|
||||
{
|
||||
/**
|
||||
* @param $source
|
||||
* @param $target
|
||||
*
|
||||
* @throws ImportFailureException In case the import failed
|
||||
*/
|
||||
public function import($source, $target);
|
||||
}
|
108
lib/Alchemy/Phrasea/Plugin/Management/AutoloaderGenerator.php
Normal file
108
lib/Alchemy/Phrasea/Plugin/Management/AutoloaderGenerator.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Management;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Exception\RegistrationFailureException;
|
||||
|
||||
class AutoloaderGenerator
|
||||
{
|
||||
private $pluginDirectory;
|
||||
|
||||
public function __construct($pluginDirectory)
|
||||
{
|
||||
$this->pluginDirectory = $pluginDirectory;
|
||||
}
|
||||
|
||||
public function write($manifests)
|
||||
{
|
||||
$this
|
||||
->doWrite('autoload.php', $this->createLoader($manifests))
|
||||
->doWrite('services.php', $this->createServices($manifests));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function doWrite($file, $data)
|
||||
{
|
||||
if (false === file_put_contents($this->pluginDirectory . DIRECTORY_SEPARATOR . $file, $data)) {
|
||||
throw new RegistrationFailureException(sprintf('Failed to write %s', $file));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function createLoader($manifests)
|
||||
{
|
||||
$buffer = <<<EOF
|
||||
<?php
|
||||
|
||||
// This file is automatically generated, please do not edit it.
|
||||
// To update configuration, use bin/console plugins:* commands.
|
||||
|
||||
return call_user_func(function () {
|
||||
EOF;
|
||||
|
||||
foreach ($manifests as $manifest) {
|
||||
$autoloader = '/' . $manifest->getName() . DIRECTORY_SEPARATOR . "vendor" . DIRECTORY_SEPARATOR . "autoload.php";
|
||||
$buffer .= <<<EOF
|
||||
|
||||
require __DIR__ . '$autoloader';
|
||||
EOF;
|
||||
}
|
||||
|
||||
// composer loader are preprent
|
||||
$autoloader = '/../vendor/autoload.php';
|
||||
$buffer .= <<<EOF
|
||||
|
||||
\$loader = require __DIR__ . '$autoloader';
|
||||
|
||||
return \$loader;\n});
|
||||
|
||||
EOF;
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
private function createServices($manifests)
|
||||
{
|
||||
$buffer = <<<EOF
|
||||
<?php
|
||||
|
||||
// This file is automatically generated, please do not edit it.
|
||||
// To update configuration, use bin/console plugins:* commands.
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
|
||||
return call_user_func(function (Application \$app) {
|
||||
|
||||
EOF;
|
||||
|
||||
foreach ($manifests as $manifest) {
|
||||
foreach ($manifest->getServices() as $service) {
|
||||
$class = $service['class'];
|
||||
$buffer .= <<<EOF
|
||||
\$app->register($class::create(\$app));
|
||||
EOF;
|
||||
}
|
||||
}
|
||||
|
||||
$buffer .= <<<EOF
|
||||
|
||||
|
||||
return \$app;
|
||||
}, \$app);
|
||||
|
||||
EOF;
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
124
lib/Alchemy/Phrasea/Plugin/Management/ComposerInstaller.php
Normal file
124
lib/Alchemy/Phrasea/Plugin/Management/ComposerInstaller.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Management;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Exception\ComposerInstallException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
|
||||
use Guzzle\Common\Exception\GuzzleException;
|
||||
use Guzzle\Http\Client as Guzzle;
|
||||
|
||||
class ComposerInstaller
|
||||
{
|
||||
private $composer;
|
||||
private $guzzle;
|
||||
private $pluginsDirectory;
|
||||
private $phpExecutable;
|
||||
|
||||
public function __construct($pluginsDirectory, Guzzle $guzzle, $phpExecutable)
|
||||
{
|
||||
if (!is_executable($phpExecutable)) {
|
||||
throw new ComposerInstallException(sprintf('`%s` is not a valid PHP executable', $phpExecutable));
|
||||
}
|
||||
|
||||
$this->guzzle = $guzzle;
|
||||
$this->pluginsDirectory = $pluginsDirectory;
|
||||
$this->phpExecutable = $phpExecutable;
|
||||
$this->composer = $this->pluginsDirectory . DIRECTORY_SEPARATOR . 'composer.phar';
|
||||
}
|
||||
|
||||
public function install($directory)
|
||||
{
|
||||
$process = $this->createProcessBuilder()
|
||||
->add('install')
|
||||
->add('--working-dir')
|
||||
->add($directory)
|
||||
->add('--no-dev')
|
||||
->add('--optimize-autoloader')
|
||||
->getProcess();
|
||||
|
||||
try {
|
||||
$process->run();
|
||||
} catch (ProcessException $e) {
|
||||
throw new ComposerInstallException(sprintf('Unable to composer install %s', $directory), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new ComposerInstallException(sprintf('Unable to composer install %s', $directory));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ProcessBuilder
|
||||
*/
|
||||
private function createProcessBuilder()
|
||||
{
|
||||
if (!file_exists($this->composer)) {
|
||||
$this->installComposer();
|
||||
} else {
|
||||
$process = ProcessBuilder::create(array(
|
||||
$this->phpExecutable, $this->composer, 'self-update'
|
||||
))->getProcess();
|
||||
$process->run();
|
||||
}
|
||||
|
||||
return ProcessBuilder::create(array($this->phpExecutable, $this->composer));
|
||||
}
|
||||
|
||||
private function installComposer()
|
||||
{
|
||||
$installer = $this->pluginsDirectory . DIRECTORY_SEPARATOR . 'installer';
|
||||
$handle = fopen($installer, 'w+');
|
||||
|
||||
$request = $this->guzzle->get('https://getcomposer.org/installer', null, $handle);
|
||||
|
||||
try {
|
||||
$response = $request->send();
|
||||
fclose($handle);
|
||||
} catch (GuzzleException $e) {
|
||||
fclose($handle);
|
||||
throw new ComposerInstallException('Unable to download composer install script.');
|
||||
}
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
@unlink($installer);
|
||||
throw new ComposerInstallException('Unable to download composer install script.');
|
||||
}
|
||||
|
||||
$dir = getcwd();
|
||||
if (!@chdir($this->pluginsDirectory)) {
|
||||
throw new ComposerInstallException('Unable to move to plugins directory for composer install.');
|
||||
}
|
||||
|
||||
$process = ProcessBuilder::create(array($this->phpExecutable, $installer))->getProcess();
|
||||
|
||||
try {
|
||||
$process->run();
|
||||
@unlink($installer);
|
||||
} catch (ProcessException $e) {
|
||||
@unlink($installer);
|
||||
throw new ComposerInstallException('Unable run composer install script.');
|
||||
}
|
||||
|
||||
if (!@chdir($dir)) {
|
||||
throw new ComposerInstallException('Unable to move to plugins directory for composer install.');
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new ComposerInstallException('Composer install failed.');
|
||||
}
|
||||
|
||||
if (!file_exists($this->composer)) {
|
||||
throw new ComposerInstallException('Composer install failed.');
|
||||
}
|
||||
}
|
||||
}
|
47
lib/Alchemy/Phrasea/Plugin/Management/PluginsExplorer.php
Normal file
47
lib/Alchemy/Phrasea/Plugin/Management/PluginsExplorer.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Management;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class PluginsExplorer implements \IteratorAggregate, \Countable
|
||||
{
|
||||
private $pluginsDirectory;
|
||||
|
||||
public function __construct($pluginsDirectory)
|
||||
{
|
||||
$this->pluginsDirectory = $pluginsDirectory;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return $this->getFinder()->getIterator();
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return $this->getFinder()->count();
|
||||
}
|
||||
|
||||
private function getFinder()
|
||||
{
|
||||
$finder = Finder::create();
|
||||
|
||||
return $finder
|
||||
->ignoreDotFiles(true)
|
||||
->ignoreVCS(true)
|
||||
->useBestAdapter()
|
||||
->directories()
|
||||
->in($this->pluginsDirectory)
|
||||
->depth(0);
|
||||
}
|
||||
}
|
29
lib/Alchemy/Phrasea/Plugin/PluginProviderInterface.php
Normal file
29
lib/Alchemy/Phrasea/Plugin/PluginProviderInterface.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
interface PluginProviderInterface extends ServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Factory for the plugin.
|
||||
*
|
||||
* This method is called to build it.
|
||||
*
|
||||
* @param Application $app
|
||||
*
|
||||
* @return PluginProviderInterface
|
||||
*/
|
||||
public static function create(Application $app);
|
||||
}
|
82
lib/Alchemy/Phrasea/Plugin/Schema/Manifest.php
Normal file
82
lib/Alchemy/Phrasea/Plugin/Schema/Manifest.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Schema;
|
||||
|
||||
class Manifest
|
||||
{
|
||||
private $data;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->get('name');
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->get('description');
|
||||
}
|
||||
|
||||
public function getKeywords()
|
||||
{
|
||||
return $this->get('keywords');
|
||||
}
|
||||
|
||||
public function getAuthors()
|
||||
{
|
||||
return $this->get('authors');
|
||||
}
|
||||
|
||||
public function getHomepage()
|
||||
{
|
||||
return $this->get('homepage');
|
||||
}
|
||||
|
||||
public function getLicense()
|
||||
{
|
||||
return $this->get('license');
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->get('version');
|
||||
}
|
||||
|
||||
public function getMinimumPhraseanetVersion()
|
||||
{
|
||||
return $this->get('minimum-phraseanet-version');
|
||||
}
|
||||
|
||||
public function getMaximumPhraseanetVersion()
|
||||
{
|
||||
return $this->get('maximum-phraseanet-version');
|
||||
}
|
||||
|
||||
public function getServices()
|
||||
{
|
||||
return $this->get('services');
|
||||
}
|
||||
|
||||
public function getExtra()
|
||||
{
|
||||
return $this->get('extra');
|
||||
}
|
||||
|
||||
private function get($key)
|
||||
{
|
||||
return isset($this->data[$key]) ? $this->data[$key] : null;
|
||||
}
|
||||
}
|
89
lib/Alchemy/Phrasea/Plugin/Schema/ManifestValidator.php
Normal file
89
lib/Alchemy/Phrasea/Plugin/Schema/ManifestValidator.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Schema;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use JsonSchema\Validator as JsonValidator;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
use Alchemy\Phrasea\Plugin\Exception\JsonValidationException;
|
||||
use Alchemy\Phrasea\Core\Version;
|
||||
|
||||
class ManifestValidator
|
||||
{
|
||||
private $validator;
|
||||
private $version;
|
||||
private $schemaData;
|
||||
|
||||
public function __construct(JsonValidator $validator, $schemaData, Version $version)
|
||||
{
|
||||
if (!is_object($schemaData)) {
|
||||
throw new InvalidArgumentException('Json Schema must be an object');
|
||||
}
|
||||
|
||||
$this->validator = $validator;
|
||||
$this->version = $version;
|
||||
$this->schemaData = $schemaData;
|
||||
}
|
||||
|
||||
public function validate($data)
|
||||
{
|
||||
if (!is_object($data)) {
|
||||
throw new InvalidArgumentException('Json Schema must be an object');
|
||||
}
|
||||
|
||||
$this->validator->reset();
|
||||
$this->validator->check($data, $this->schemaData);
|
||||
|
||||
if (!$this->validator->isValid()) {
|
||||
$errors = array();
|
||||
foreach ((array) $this->validator->getErrors() as $error) {
|
||||
$errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message'];
|
||||
}
|
||||
throw new JsonValidationException('Manifest file does not match the expected JSON schema', $errors);
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-z0-9-_]+$/i', $data->name)) {
|
||||
throw new JsonValidationException('Does not match the expected JSON schema', array('"name" must not contains only alphanumeric caracters'));
|
||||
}
|
||||
|
||||
if (isset($data->{'minimum-phraseanet-version'})) {
|
||||
if (true !== version_compare($this->version->getNumber(), $data->{'minimum-phraseanet-version'}, '>=')) {
|
||||
throw new JsonValidationException(sprintf(
|
||||
'Version incomptibility : Minimum Phraseanet version required is %s, current version is %s',
|
||||
$data->{'minimum-phraseanet-version'},
|
||||
$this->version->getNumber()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data->{'maximum-phraseanet-version'})) {
|
||||
if (true !== version_compare($this->version->getNumber(), $data->{'maximum-phraseanet-version'}, '<')) {
|
||||
throw new JsonValidationException(sprintf(
|
||||
'Version incomptibility : Maximum Phraseanet version required is %s, current version is %s',
|
||||
$data->{'maximum-phraseanet-version'},
|
||||
$this->version->getNumber()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function create(Application $app)
|
||||
{
|
||||
$data = @json_decode(@file_get_contents($app['plugins.schema']));
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new InvalidArgumentException(sprintf('Unable to read %s', $app['plugins.schema']));
|
||||
}
|
||||
|
||||
return new static($app['plugins.json-validator'], $data, $app['phraseanet.version']);
|
||||
}
|
||||
}
|
80
lib/Alchemy/Phrasea/Plugin/Schema/PluginValidator.php
Normal file
80
lib/Alchemy/Phrasea/Plugin/Schema/PluginValidator.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Schema;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
|
||||
use Alchemy\Phrasea\Plugin\Schema\Manifest;
|
||||
use Alchemy\Phrasea\Plugin\Exception\PluginValidationException;
|
||||
use Alchemy\Phrasea\Plugin\Exception\JsonValidationException;
|
||||
|
||||
class PluginValidator
|
||||
{
|
||||
private $manifestValidator;
|
||||
|
||||
public function __construct(ManifestValidator $manifestValidator)
|
||||
{
|
||||
$this->manifestValidator = $manifestValidator;
|
||||
}
|
||||
|
||||
public function validatePlugin($directory)
|
||||
{
|
||||
$this->ensureComposer($directory);
|
||||
$this->ensureManifest($directory);
|
||||
|
||||
$manifest = $directory . DIRECTORY_SEPARATOR . 'manifest.json';
|
||||
$data = @json_decode(@file_get_contents($manifest));
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new PluginValidationException(sprintf('Unable to parse file %s', $manifest));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->manifestValidator->validate($data);
|
||||
} catch (JsonValidationException $e) {
|
||||
throw new PluginValidationException('Manifest file is invalid', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return new Manifest($this->objectToArray($data));
|
||||
}
|
||||
|
||||
private function ensureManifest($directory)
|
||||
{
|
||||
$manifest = $directory . DIRECTORY_SEPARATOR . 'manifest.json';
|
||||
$this->ensureFile($manifest);
|
||||
}
|
||||
|
||||
private function ensureComposer($directory)
|
||||
{
|
||||
$composer = $directory . DIRECTORY_SEPARATOR . 'composer.json';
|
||||
$this->ensureFile($composer);
|
||||
}
|
||||
|
||||
private function ensureFile($file)
|
||||
{
|
||||
if (!file_exists($file) || !is_file($file) || !is_readable($file)) {
|
||||
throw new PluginValidationException(sprintf('Required file %s is not present.', $file));
|
||||
}
|
||||
}
|
||||
|
||||
private function objectToArray($data)
|
||||
{
|
||||
if (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
if (is_array($data)) {
|
||||
return array_map(array($this, 'objectToArray'), $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
22
lib/Alchemy/Phrasea/Plugin/resources/example.json
Normal file
22
lib/Alchemy/Phrasea/Plugin/resources/example.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Class Connector",
|
||||
"description" : "A custom class connector",
|
||||
"keywords" : ["connector"],
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Author name",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
}
|
||||
],
|
||||
"homepage" : "http://example.com/project/example",
|
||||
"license" : "MIT",
|
||||
"version" : "0.1",
|
||||
"minimum-phraseanet-version": "3.8",
|
||||
"maximum-phraseanet-version": "3.9",
|
||||
"services" : [
|
||||
{
|
||||
"class": "Vendor\\Connector\\PluginServiceInterface"
|
||||
}
|
||||
]
|
||||
}
|
3
lib/autoload.php
Normal file
3
lib/autoload.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
return require __DIR__ . '/../plugins/autoload.php';
|
96
lib/conf.d/plugin-schema.json
Normal file
96
lib/conf.d/plugin-schema.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"name": "Phraseanet Plugin Schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the plugin",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The description of the plugin",
|
||||
"required": true
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"description": "An array of keywords",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A keyword"
|
||||
}
|
||||
},
|
||||
"authors": {
|
||||
"type": "array",
|
||||
"description": "An array of authors",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "An author",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The author name",
|
||||
"required": true
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "The author email",
|
||||
"format": "email"
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string",
|
||||
"description": "The author website URL",
|
||||
"format": "uri"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string",
|
||||
"description": "The homepage of the plugin"
|
||||
},
|
||||
"license": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The license of the plugin"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The version of the plugin"
|
||||
},
|
||||
"minimum-phraseanet-version": {
|
||||
"type": "string",
|
||||
"description": "The minimum phraseanet version for the plugin, including the one provided."
|
||||
},
|
||||
"maximum-phraseanet-version": {
|
||||
"type": "string",
|
||||
"description": "The maximum phraseanet version for the plugin, excluding the one provided"
|
||||
},
|
||||
"services": {
|
||||
"type": "array",
|
||||
"description": "An array of services to register.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A service",
|
||||
"properties": {
|
||||
"class": {
|
||||
"type": "string",
|
||||
"description": "The plugin service provider"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"type": [
|
||||
"object",
|
||||
"array"
|
||||
],
|
||||
"description": "Arbitrary extra data that can be used by custom installers.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
5
plugins/autoload.php
Normal file
5
plugins/autoload.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return call_user_func(function () {
|
||||
return require __DIR__ . '/../vendor/autoload.php';
|
||||
});
|
7
plugins/services.php
Normal file
7
plugins/services.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
|
||||
return call_user_func(function (Application $app) {
|
||||
return $app;
|
||||
}, $app);
|
76
tests/Alchemy/Tests/Phrasea/Command/Plugin/AddPluginTest.php
Normal file
76
tests/Alchemy/Tests/Phrasea/Command/Plugin/AddPluginTest.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Command\Plugin;
|
||||
|
||||
use Alchemy\Phrasea\Command\Plugin\AddPlugin;
|
||||
|
||||
class AddPluginTest extends PluginCommandTestCase
|
||||
{
|
||||
public function testExecute()
|
||||
{
|
||||
$source = 'TestPlugin';
|
||||
|
||||
$input = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
||||
$input->expects($this->once())
|
||||
->method('getArgument')
|
||||
->with($this->equalTo('source'))
|
||||
->will($this->returnValue($source));
|
||||
|
||||
$output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
||||
|
||||
$command = new AddPlugin();
|
||||
$command->setContainer(self::$DI['app']);
|
||||
|
||||
$manifest = $this->createManifestMock();
|
||||
$manifest->expects($this->any())
|
||||
->method('getName')
|
||||
->will($this->returnValue($source));
|
||||
|
||||
self::$DI['app']['temporary-filesystem'] = $this->createTemporaryFilesystemMock();
|
||||
self::$DI['app']['plugins.autoloader-generator'] = $this->createPluginsAutoloaderGeneratorMock();
|
||||
self::$DI['app']['plugins.explorer'] = array(self::$DI['app']['plugins.directory'].'/TestPlugin');
|
||||
self::$DI['app']['plugins.plugins-validator'] = $this->createPluginsValidatorMock();
|
||||
self::$DI['app']['filesystem'] = $this->createFilesystemMock();
|
||||
self::$DI['app']['plugins.composer-installer'] = $this->createComposerInstallerMock();
|
||||
self::$DI['app']['plugins.importer'] = $this->createPluginsImporterMock();
|
||||
|
||||
self::$DI['app']['temporary-filesystem']->expects($this->once())
|
||||
->method('createTemporaryDirectory')
|
||||
->will($this->returnValue('tempdir'));
|
||||
|
||||
self::$DI['app']['plugins.importer']->expects($this->once())
|
||||
->method('import')
|
||||
->with($source, 'tempdir');
|
||||
|
||||
// the plugin is checked when updating config files
|
||||
self::$DI['app']['plugins.plugins-validator']->expects($this->at(0))
|
||||
->method('validatePlugin')
|
||||
->with('tempdir')
|
||||
->will($this->returnValue($manifest));
|
||||
|
||||
self::$DI['app']['plugins.plugins-validator']->expects($this->at(1))
|
||||
->method('validatePlugin')
|
||||
->with(self::$DI['app']['plugins.directory'].'/TestPlugin')
|
||||
->will($this->returnValue($manifest));
|
||||
|
||||
self::$DI['app']['plugins.composer-installer']->expects($this->once())
|
||||
->method('install')
|
||||
->with('tempdir');
|
||||
|
||||
self::$DI['app']['filesystem']->expects($this->at(0))
|
||||
->method('mirror')
|
||||
->with('tempdir', self::$DI['app']['plugins.directory'].'/TestPlugin');
|
||||
|
||||
self::$DI['app']['filesystem']->expects($this->at(1))
|
||||
->method('remove')
|
||||
->with('tempdir');
|
||||
|
||||
self::$DI['app']['plugins.autoloader-generator']->expects($this->once())
|
||||
->method('write')
|
||||
->with(array($manifest));
|
||||
|
||||
$result = $command->execute($input, $output);
|
||||
|
||||
$this->assertSame(0, $result);
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Command\Plugin;
|
||||
|
||||
class PluginCommandTestCase extends \PhraseanetPHPUnitAbstract
|
||||
{
|
||||
protected function createTemporaryFilesystemMock()
|
||||
{
|
||||
return $this->getMockBuilder('Neutron\TemporaryFilesystem\TemporaryFilesystem')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createPluginsImporterMock()
|
||||
{
|
||||
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Importer\Importer')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createPluginsValidatorMock()
|
||||
{
|
||||
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Schema\PluginValidator')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createManifestMock()
|
||||
{
|
||||
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Schema\Manifest')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createComposerInstallerMock()
|
||||
{
|
||||
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Management\ComposerInstaller')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createFilesystemMock()
|
||||
{
|
||||
return $this->getMockBuilder('Symfony\Component\Filesystem\Filesystem')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createPluginsExplorerMock()
|
||||
{
|
||||
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Management\PluginsExplorer')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function createPluginsAutoloaderGeneratorMock()
|
||||
{
|
||||
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Management\AutoloaderGenerator')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Command\Plugin;
|
||||
|
||||
use Alchemy\Phrasea\Command\Plugin\RemovePlugin;
|
||||
|
||||
class RemovePluginTest extends PluginCommandTestCase
|
||||
{
|
||||
public function testExecute()
|
||||
{
|
||||
$name = 'TestPlugin';
|
||||
|
||||
$input = $this->getMock('Symfony\Component\Console\Input\InputInterface');
|
||||
$input->expects($this->once())
|
||||
->method('getArgument')
|
||||
->with($this->equalTo('name'))
|
||||
->will($this->returnValue($name));
|
||||
|
||||
$output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
|
||||
|
||||
$command = new RemovePlugin();
|
||||
$command->setContainer(self::$DI['app']);
|
||||
|
||||
self::$DI['app']['filesystem'] = $this->createFilesystemMock();
|
||||
self::$DI['app']['filesystem']->expects($this->once())
|
||||
->method('remove')
|
||||
->with(self::$DI['app']['plugins.directory'].'/'.$name);
|
||||
|
||||
$result = $command->execute($input, $output);
|
||||
|
||||
$this->assertSame(0, $result);
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Core\Provider;
|
||||
|
||||
use Alchemy\Phrasea\Core\Provider\PluginServiceProvider;
|
||||
use Silex\Application;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
|
||||
/**
|
||||
* @covers Alchemy\Phrasea\Core\Provider\PluginServiceProvider
|
||||
*/
|
||||
class PluginServiceProvidertest extends ServiceProviderTestCase
|
||||
{
|
||||
public function provideServiceDescription()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.json-validator',
|
||||
'JsonSchema\Validator'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.plugins-validator',
|
||||
'Alchemy\Phrasea\Plugin\Schema\PluginValidator'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.manifest-validator',
|
||||
'Alchemy\Phrasea\Plugin\Schema\ManifestValidator'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.import-strategy',
|
||||
'Alchemy\Phrasea\Plugin\Importer\ImportStrategy'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.autoloader-generator',
|
||||
'Alchemy\Phrasea\Plugin\Management\AutoloaderGenerator'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.guzzle',
|
||||
'Guzzle\Http\Client'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.composer-installer',
|
||||
'Alchemy\Phrasea\Plugin\Management\ComposerInstaller'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.explorer',
|
||||
'Alchemy\Phrasea\Plugin\Management\PluginsExplorer'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.importer',
|
||||
'Alchemy\Phrasea\Plugin\Importer\Importer'
|
||||
),
|
||||
array(
|
||||
'Alchemy\Phrasea\Core\Provider\PluginServiceProvider',
|
||||
'plugins.importer.folder-importer',
|
||||
'Alchemy\Phrasea\Plugin\Importer\FolderImporter'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function testSchemaIsDefined()
|
||||
{
|
||||
$app = new Application();
|
||||
$app->register(new PluginServiceProvider());
|
||||
|
||||
$this->assertFileExists($app['plugins.schema']);
|
||||
$this->assertTrue(is_file($app['plugins.schema']));
|
||||
}
|
||||
|
||||
public function testPluginDirIsDefined()
|
||||
{
|
||||
$app = new Application();
|
||||
$app->register(new PluginServiceProvider());
|
||||
|
||||
$this->assertFileExists($app['plugins.directory']);
|
||||
$this->assertTrue(is_dir($app['plugins.directory']));
|
||||
}
|
||||
|
||||
public function testInstallerUsesPhpConf()
|
||||
{
|
||||
$finder = new ExecutableFinder();
|
||||
$php = $finder->find('php');
|
||||
|
||||
if (null === $php) {
|
||||
$this->markTestSkipped('Unable to detect PHP binary');
|
||||
}
|
||||
|
||||
$app = new Application();
|
||||
$app['phraseanet.registry'] = $this->createRegistryMock();
|
||||
$app['phraseanet.registry']->expects($this->once())
|
||||
->method('get')
|
||||
->with('php_binary')
|
||||
->will($this->returnValue($php));
|
||||
|
||||
$app->register(new PluginServiceProvider());
|
||||
|
||||
$this->assertInstanceOf('Alchemy\Phrasea\Plugin\Management\ComposerInstaller', $app['plugins.composer-installer']);
|
||||
}
|
||||
|
||||
public function testInstallerCanDetectPhpConf()
|
||||
{
|
||||
$app = new Application();
|
||||
$app['phraseanet.registry'] = $this->createRegistryMock();
|
||||
$app['phraseanet.registry']->expects($this->once())
|
||||
->method('get')
|
||||
->with('php_binary')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
$app->register(new PluginServiceProvider());
|
||||
|
||||
$this->assertInstanceOf('Alchemy\Phrasea\Plugin\Management\ComposerInstaller', $app['plugins.composer-installer']);
|
||||
}
|
||||
|
||||
private function createRegistryMock()
|
||||
{
|
||||
return $this->getMockBuilder('registry')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Core\Provider;
|
||||
|
||||
/**
|
||||
* @covers Alchemy\Phrasea\Core\Provider\TemporaryFilesystemServiceProvider
|
||||
*/
|
||||
class TemporaryFilesystemServiceProvidertest extends ServiceProviderTestCase
|
||||
{
|
||||
public function provideServiceDescription()
|
||||
{
|
||||
return array(
|
||||
array('Alchemy\Phrasea\Core\Provider\TemporaryFilesystemServiceProvider', 'temporary-filesystem', 'Neutron\TemporaryFilesystem\TemporaryFilesystem'),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name" : "test/test",
|
||||
"description" : "test file",
|
||||
"license" : "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Vendor" : "src"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"keywords" : ["connector"],
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Author name",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
}
|
||||
],
|
||||
"homepage" : "http://example.com/project/example",
|
||||
"license" : "MIT",
|
||||
"version" : "0.1",
|
||||
"minimum-phraseanet-version": "3.8",
|
||||
"maximum-phraseanet-version": "3.9",
|
||||
"services" : [
|
||||
{
|
||||
"class": "Vendor\\PluginService"
|
||||
}
|
||||
],
|
||||
"extra" : {
|
||||
"property" : "value"
|
||||
}
|
||||
}
|
24
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/PluginDir/TestPlugin/src/Vendor/PluginService.php
vendored
Normal file
24
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/PluginDir/TestPlugin/src/Vendor/PluginService.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Vendor;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Silex\Application;
|
||||
use Alchemy\Phrasea\Plugin\PluginProviderInterface;
|
||||
|
||||
class PluginService implements PluginProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugin-test'] = 'hello world';
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
public static function create(PhraseaApplication $app)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name" : "test/test",
|
||||
"description" : "test file",
|
||||
"license" : "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Vendor" : "src"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"keywords" : ["connector"],
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Author name",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
}
|
||||
],
|
||||
"homepage" : "http://example.com/project/example",
|
||||
"license" : "MIT",
|
||||
"version" : "0.1",
|
||||
"minimum-phraseanet-version": "3.8",
|
||||
"maximum-phraseanet-version": "3.9",
|
||||
"services" : [
|
||||
{
|
||||
"class": "Vendor\\PluginService"
|
||||
}
|
||||
]
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Vendor;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Silex\Application;
|
||||
use Alchemy\Phrasea\Plugin\PluginProviderInterface;
|
||||
|
||||
class PluginService implements PluginProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugin-test'] = 'hello world';
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
public static function create(PhraseaApplication $app)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer' . '/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit7a818310afafc4600ddb36d030b5d99d::getLoader();
|
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0 class loader
|
||||
*
|
||||
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
private $prefixes = array();
|
||||
private $fallbackDirs = array();
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
return call_user_func_array('array_merge', $this->prefixes);
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirs;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of classes, merging with any others previously set.
|
||||
*
|
||||
* @param string $prefix The classes prefix
|
||||
* @param array|string $paths The location(s) of the classes
|
||||
* @param bool $prepend Prepend the location(s)
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirs = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirs
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirs = array_merge(
|
||||
$this->fallbackDirs,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixes[$first][$prefix])) {
|
||||
$this->prefixes[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixes[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixes[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixes[$first][$prefix] = array_merge(
|
||||
$this->prefixes[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of classes, replacing any others previously set.
|
||||
*
|
||||
* @param string $prefix The classes prefix
|
||||
* @param array|string $paths The location(s) of the classes
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirs = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
$this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
include $file;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
|
||||
if ('\\' == $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
$className = substr($class, $pos + 1);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$classPath = null;
|
||||
$className = $class;
|
||||
}
|
||||
|
||||
$classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixes[$first])) {
|
||||
foreach ($this->prefixes[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
|
||||
return $dir . DIRECTORY_SEPARATOR . $classPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->fallbackDirs as $dir) {
|
||||
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
|
||||
return $dir . DIRECTORY_SEPARATOR . $classPath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->classMap[$class] = false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Vendor\\PluginService' => $baseDir . '/src/Vendor/PluginService.php',
|
||||
);
|
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Vendor' => array($baseDir . '/src'),
|
||||
);
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit7a818310afafc4600ddb36d030b5d99d
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit7a818310afafc4600ddb36d030b5d99d', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit7a818310afafc4600ddb36d030b5d99d', 'loadClassLoader'));
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name" : "test/test",
|
||||
"description" : "test file",
|
||||
"license" : "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Vendor" : "src"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Test Plugin",
|
||||
"description" : "A custom class connector",
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Vendor;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Silex\Application;
|
||||
use Alchemy\Phrasea\Plugin\PluginProviderInterface;
|
||||
|
||||
class PluginService implements PluginProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugin-test'] = 'hello world';
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
public static function create(PhraseaApplication $app)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"keywords" : ["connector"],
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Author name",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
}
|
||||
],
|
||||
"homepage" : "http://example.com/project/example",
|
||||
"license" : "MIT",
|
||||
"version" : "0.1",
|
||||
"minimum-phraseanet-version": "3.8",
|
||||
"maximum-phraseanet-version": "3.9",
|
||||
"services" : [
|
||||
{
|
||||
"class": "Vendor\\PluginService"
|
||||
}
|
||||
],
|
||||
"extra" : {
|
||||
"property" : "value"
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Vendor;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Silex\Application;
|
||||
use Alchemy\Phrasea\Plugin\PluginProviderInterface;
|
||||
|
||||
class PluginService implements PluginProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugin-test'] = 'hello world';
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
public static function create(PhraseaApplication $app)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name" : "test/test",
|
||||
"description" : "test file",
|
||||
"license" : "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Vendor" : "src"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Vendor;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Silex\Application;
|
||||
use Alchemy\Phrasea\Plugin\PluginProviderInterface;
|
||||
|
||||
class PluginService implements PluginProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugin-test'] = 'hello world';
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
public static function create(PhraseaApplication $app)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name" : "test/test",
|
||||
"description" : "test file",
|
||||
"license" : "MIT",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Vendor" : "src"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
Hello Manifest
|
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Vendor;
|
||||
|
||||
use Alchemy\Phrasea\Application as PhraseaApplication;
|
||||
use Silex\Application;
|
||||
use Alchemy\Phrasea\Plugin\PluginProviderInterface;
|
||||
|
||||
class PluginService implements PluginProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
$app['plugin-test'] = 'hello world';
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
|
||||
public static function create(PhraseaApplication $app)
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"keywords" : ["connector", "test"],
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Author name",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
},
|
||||
{
|
||||
"name" : "Author name2",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
}
|
||||
],
|
||||
"homepage" : "http://example.com/project/example",
|
||||
"license" : "MIT",
|
||||
"version" : "0.1",
|
||||
"minimum-phraseanet-version": "3.8",
|
||||
"maximum-phraseanet-version": "3.9",
|
||||
"services" : [
|
||||
{
|
||||
"class": "Vendor\\PluginService"
|
||||
},
|
||||
{
|
||||
"class": "Vendor\\PluginService2"
|
||||
}
|
||||
],
|
||||
"extra" : {
|
||||
"property" : "value"
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector"
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"description": "pretty one"
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"authors" : {
|
||||
"name" : "Author name",
|
||||
"homepage" : "http://example.com",
|
||||
"email" : "email@example.com"
|
||||
},
|
||||
"services" : {
|
||||
"class": "Vendor\\PluginService"
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Test Plugin",
|
||||
"description" : "A custom class connector"
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"minimum-phraseanet-version": "14"
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "TestPlugin",
|
||||
"description" : "A custom class connector",
|
||||
"maximum-phraseanet-version": "3.8"
|
||||
}
|
7
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/autoload.php
vendored
Normal file
7
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/autoload.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer' . '/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit4ea6c38a75e30f622d666844c915395f::getLoader();
|
246
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/ClassLoader.php
vendored
Normal file
246
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/ClassLoader.php
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0 class loader
|
||||
*
|
||||
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
private $prefixes = array();
|
||||
private $fallbackDirs = array();
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
return call_user_func_array('array_merge', $this->prefixes);
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirs;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of classes, merging with any others previously set.
|
||||
*
|
||||
* @param string $prefix The classes prefix
|
||||
* @param array|string $paths The location(s) of the classes
|
||||
* @param bool $prepend Prepend the location(s)
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirs = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirs
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirs = array_merge(
|
||||
$this->fallbackDirs,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixes[$first][$prefix])) {
|
||||
$this->prefixes[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixes[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixes[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixes[$first][$prefix] = array_merge(
|
||||
$this->prefixes[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of classes, replacing any others previously set.
|
||||
*
|
||||
* @param string $prefix The classes prefix
|
||||
* @param array|string $paths The location(s) of the classes
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirs = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
$this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
include $file;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
|
||||
if ('\\' == $class[0]) {
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
$className = substr($class, $pos + 1);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$classPath = null;
|
||||
$className = $class;
|
||||
}
|
||||
|
||||
$classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixes[$first])) {
|
||||
foreach ($this->prefixes[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
|
||||
return $dir . DIRECTORY_SEPARATOR . $classPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->fallbackDirs as $dir) {
|
||||
if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
|
||||
return $dir . DIRECTORY_SEPARATOR . $classPath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->classMap[$class] = false;
|
||||
}
|
||||
}
|
9
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/autoload_classmap.php
vendored
Normal file
9
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/autoload_classmap.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
9
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/autoload_namespaces.php
vendored
Normal file
9
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/autoload_namespaces.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
43
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/autoload_real.php
vendored
Normal file
43
tests/Alchemy/Tests/Phrasea/Plugin/Fixtures/vendor/composer/autoload_real.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit4ea6c38a75e30f622d666844c915395f
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit4ea6c38a75e30f622d666844c915395f', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit4ea6c38a75e30f622d666844c915395f', 'loadClassLoader'));
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Importer\FolderImporter;
|
||||
use Alchemy\Tests\Phrasea\Plugin\PluginTestCase;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
|
||||
class FolderImporterTest extends PluginTestCase
|
||||
{
|
||||
public function testImport()
|
||||
{
|
||||
$fs = $this->createFilesystemMock();
|
||||
|
||||
$source = 'test-plugin';
|
||||
$target = __DIR__;
|
||||
|
||||
$fs->expects($this->once())
|
||||
->method('mirror')
|
||||
->with($source, $target);
|
||||
|
||||
$importer = new FolderImporter($fs);
|
||||
$importer->import($source, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Alchemy\Phrasea\Plugin\Exception\ImportFailureException
|
||||
*/
|
||||
public function testImportFailed()
|
||||
{
|
||||
$fs = $this->createFilesystemMock();
|
||||
|
||||
$source = 'test-plugin';
|
||||
$target = __DIR__;
|
||||
|
||||
$fs->expects($this->once())
|
||||
->method('mirror')
|
||||
->with($source, $target)
|
||||
->will($this->throwException(new IOException('Error')));
|
||||
|
||||
$importer = new FolderImporter($fs);
|
||||
$importer->import($source, $target);
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Importer\ImportStrategy;
|
||||
use Alchemy\Tests\Phrasea\Plugin\PluginTestCase;
|
||||
|
||||
class ImportStrategyTest extends PluginTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideFolderSources
|
||||
*/
|
||||
public function testDetect($source)
|
||||
{
|
||||
$importer = new ImportStrategy();
|
||||
$this->assertEquals('plugins.importer.folder-importer', $importer->detect($source));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideInvalidFolderSources
|
||||
* @expectedException Alchemy\Phrasea\Plugin\Exception\ImportFailureException
|
||||
*/
|
||||
public function testDetectFailure($source)
|
||||
{
|
||||
$importer = new ImportStrategy();
|
||||
$importer->detect($source);
|
||||
}
|
||||
|
||||
public function provideFolderSources()
|
||||
{
|
||||
return array(
|
||||
array(__DIR__),
|
||||
array(dirname(__DIR__)),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideInvalidFolderSources()
|
||||
{
|
||||
return array(
|
||||
array('/path/to/invalid/dir'),
|
||||
array(__FILE__),
|
||||
);
|
||||
}
|
||||
}
|
51
tests/Alchemy/Tests/Phrasea/Plugin/Importer/ImporterTest.php
Normal file
51
tests/Alchemy/Tests/Phrasea/Plugin/Importer/ImporterTest.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Importer;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Importer\Importer;
|
||||
use Alchemy\Tests\Phrasea\Plugin\PluginTestCase;
|
||||
|
||||
class ImporterTest extends PluginTestCase
|
||||
{
|
||||
public function testImport()
|
||||
{
|
||||
$source = 'here';
|
||||
$target = 'there';
|
||||
|
||||
$strategy = $this->getMock('Alchemy\Phrasea\Plugin\Importer\ImportStrategy');
|
||||
$strategy->expects($this->once())
|
||||
->method('detect')
|
||||
->with($source)
|
||||
->will($this->returnValue('elephant'));
|
||||
|
||||
$importerInterface = $this->getMock('Alchemy\Phrasea\Plugin\Importer\ImporterInterface');
|
||||
$importerInterface->expects($this->once())
|
||||
->method('import')
|
||||
->with($source, $target);
|
||||
|
||||
$importer = new Importer($strategy, array('elephant' => $importerInterface));
|
||||
$importer->import($source, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Alchemy\Phrasea\Plugin\Exception\ImportFailureException
|
||||
*/
|
||||
public function testImportFailure()
|
||||
{
|
||||
$source = 'here';
|
||||
$target = 'there';
|
||||
|
||||
$strategy = $this->getMock('Alchemy\Phrasea\Plugin\Importer\ImportStrategy');
|
||||
$strategy->expects($this->once())
|
||||
->method('detect')
|
||||
->with($source)
|
||||
->will($this->returnValue('elephant'));
|
||||
|
||||
$importerInterface = $this->getMock('Alchemy\Phrasea\Plugin\Importer\ImporterInterface');
|
||||
$importerInterface->expects($this->never())
|
||||
->method('import');
|
||||
|
||||
$importer = new Importer($strategy, array('rhinoceros' => $importerInterface));
|
||||
$importer->import($source, $target);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Management;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\Plugin\Management\AutoloaderGenerator;
|
||||
use Alchemy\Phrasea\Plugin\Schema\Manifest;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
|
||||
class AutoloaderGeneratorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGeneratedFileAfterInstall()
|
||||
{
|
||||
$pluginDir = __DIR__ . '/../Fixtures/PluginDirInstalled/TestPlugin';
|
||||
$pluginsDir = __DIR__ . '/../Fixtures/PluginDirInstalled';
|
||||
|
||||
$files = array($pluginsDir . '/services.php', $pluginsDir . '/autoload.php');
|
||||
|
||||
$this->cleanup($files);
|
||||
|
||||
$generator = new AutoloaderGenerator($pluginsDir);
|
||||
$generator->write(array(new Manifest(json_decode(file_get_contents($pluginDir . '/manifest.json'), true))));
|
||||
|
||||
$finder = new ExecutableFinder();
|
||||
$php = $finder->find('php');
|
||||
|
||||
if (null === $php) {
|
||||
$this->markTestSkipped('Php executable not found.');
|
||||
}
|
||||
|
||||
foreach ($files as $file ) {
|
||||
$this->assertFileExists($file);
|
||||
$process = ProcessBuilder::create(array($php, '-l', $file))->getProcess();
|
||||
$process->run();
|
||||
$this->assertTrue($process->isSuccessful(), basename($file) . ' is valid');
|
||||
}
|
||||
|
||||
// test autoload
|
||||
$this->assertFalse(class_exists('Vendor\PluginService'));
|
||||
$loader = require $pluginsDir . '/autoload.php';
|
||||
$this->assertInstanceOf('Composer\Autoload\ClassLoader', $loader);
|
||||
$this->assertTrue(class_exists('Vendor\PluginService'));
|
||||
|
||||
// load services
|
||||
$app = new Application();
|
||||
$retrievedApp = require $pluginsDir . '/services.php';
|
||||
|
||||
$this->assertSame($app, $retrievedApp);
|
||||
$this->assertEquals('hello world', $app['plugin-test']);
|
||||
|
||||
$this->cleanup($files);
|
||||
}
|
||||
|
||||
private function cleanup($files)
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\Plugin\Management;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Management\ComposerInstaller;
|
||||
use Guzzle\Http\Client as Guzzle;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class ComposerInstallerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testInstall()
|
||||
{
|
||||
$fs = new Filesystem();
|
||||
|
||||
$finder = new ExecutableFinder();
|
||||
$php = $finder->find('php');
|
||||
|
||||
$vendorDir = __DIR__ . '/../Fixtures/PluginDir/TestPlugin/vendor';
|
||||
$installFile = __DIR__ . '/installer';
|
||||
$composer = __DIR__ . '/composer.phar';
|
||||
|
||||
$fs->remove(array($composer, $installFile, $vendorDir));
|
||||
|
||||
if (null === $php) {
|
||||
$this->markTestSkipped('Unable to find PHP executable.');
|
||||
}
|
||||
|
||||
$installer = new ComposerInstaller(__DIR__, new Guzzle(), $php);
|
||||
$installer->install(__DIR__ . '/../Fixtures/PluginDir/TestPlugin');
|
||||
|
||||
$this->assertFileExists($composer);
|
||||
unlink($composer);
|
||||
|
||||
$this->assertFileNotExists($installFile);
|
||||
$this->assertFileExists($vendorDir);
|
||||
|
||||
$fs->remove($vendorDir);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Management;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Management\PluginsExplorer;
|
||||
use Alchemy\Tests\Phrasea\Plugin\PluginTestCase;
|
||||
|
||||
class PluginsExplorerTest extends PluginTestCase
|
||||
{
|
||||
public function testCount()
|
||||
{
|
||||
$explorer = new PluginsExplorer(__DIR__ . '/../Fixtures/PluginDir');
|
||||
|
||||
$this->assertCount(1, $explorer);
|
||||
}
|
||||
|
||||
public function testGetIterator()
|
||||
{
|
||||
$explorer = new PluginsExplorer(__DIR__ . '/../Fixtures/PluginDir');
|
||||
|
||||
$dirs = array();
|
||||
|
||||
foreach ($explorer as $dir) {
|
||||
$dirs[] = (string) realpath($dir);
|
||||
}
|
||||
|
||||
$this->assertSame(array(realpath(__DIR__ . '/../Fixtures/PluginDir/TestPlugin')), $dirs);
|
||||
}
|
||||
}
|
33
tests/Alchemy/Tests/Phrasea/Plugin/PluginTestCase.php
Normal file
33
tests/Alchemy/Tests/Phrasea/Plugin/PluginTestCase.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
|
||||
|
||||
class PluginTestCase extends \PhraseanetPHPUnitAbstract
|
||||
{
|
||||
protected function createManifestValidator()
|
||||
{
|
||||
return ManifestValidator::create(self::$DI['app']);
|
||||
}
|
||||
|
||||
protected function getPluginDirectory()
|
||||
{
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'PluginFolder';
|
||||
}
|
||||
|
||||
protected function createFilesystemMock()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\Filesystem\Filesystem');
|
||||
}
|
||||
|
||||
protected function getSchema()
|
||||
{
|
||||
return file_get_contents($this->getSchemaPath());
|
||||
}
|
||||
|
||||
protected function getSchemaPath()
|
||||
{
|
||||
return __DIR__ . '/../../../../../lib/conf.d/plugin-schema.json';
|
||||
}
|
||||
}
|
30
tests/Alchemy/Tests/Phrasea/Plugin/Schema/ManifestTest.php
Normal file
30
tests/Alchemy/Tests/Phrasea/Plugin/Schema/ManifestTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Schema;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Schema\Manifest;
|
||||
|
||||
class ManifestTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetters()
|
||||
{
|
||||
$data = json_decode(file_get_contents(__DIR__ . '/../Fixtures/PluginDir/TestPlugin/manifest.json'), true);
|
||||
$manifest = new Manifest($data);
|
||||
|
||||
$this->assertEquals('TestPlugin', $manifest->getName());
|
||||
$this->assertEquals('A custom class connector', $manifest->getDescription());
|
||||
$this->assertEquals(array('connector'), $manifest->getKeywords());
|
||||
$this->assertEquals(array(array(
|
||||
'name' => 'Author name',
|
||||
'homepage' => 'http://example.com',
|
||||
'email' => 'email@example.com',
|
||||
)), $manifest->getAuthors());
|
||||
$this->assertEquals('http://example.com/project/example', $manifest->getHomepage());
|
||||
$this->assertEquals('MIT', $manifest->getLicense());
|
||||
$this->assertEquals('0.1', $manifest->getVersion());
|
||||
$this->assertEquals('3.8', $manifest->getMinimumPhraseanetVersion());
|
||||
$this->assertEquals('3.9', $manifest->getMaximumPhraseanetVersion());
|
||||
$this->assertEquals(array(array('class' => 'Vendor\PluginService')), $manifest->getServices());
|
||||
$this->assertEquals(array('property' => 'value'), $manifest->getExtra());
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Schema;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
|
||||
use JsonSchema\Validator as JsonSchemaValidator;
|
||||
use Alchemy\Tests\Phrasea\Plugin\PluginTestCase;
|
||||
|
||||
class ManifestValidatorTest extends PluginTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideGoodManifestFiles
|
||||
*/
|
||||
public function testValidateGoodOnes($file)
|
||||
{
|
||||
$validator = $this->createValidator();
|
||||
$validator->validate(json_decode(file_get_contents($file)));
|
||||
}
|
||||
|
||||
public function provideGoodManifestFiles()
|
||||
{
|
||||
return array(
|
||||
array(__DIR__ . '/../Fixtures/manifest-good-big.json'),
|
||||
array(__DIR__ . '/../Fixtures/manifest-good-minimal.json'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Alchemy\Phrasea\Plugin\Exception\JsonValidationException
|
||||
* @dataProvider provideWrongManifestFiles
|
||||
*/
|
||||
public function testValidateWrongOnes($file)
|
||||
{
|
||||
$validator = $this->createValidator();
|
||||
$validator->validate(json_decode(file_get_contents($file)));
|
||||
}
|
||||
|
||||
public function provideWrongManifestFiles()
|
||||
{
|
||||
return array(
|
||||
array(__DIR__ . '/../Fixtures/manifest-wrong1.json'),
|
||||
array(__DIR__ . '/../Fixtures/manifest-wrong2.json'),
|
||||
array(__DIR__ . '/../Fixtures/manifest-wrong3.json'),
|
||||
array(__DIR__ . '/../Fixtures/manifest-wrong4.json'),
|
||||
array(__DIR__ . '/../Fixtures/manifest-wrong5-min-version.json'),
|
||||
array(__DIR__ . '/../Fixtures/manifest-wrong6-max-version.json'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testValidateInvalidData()
|
||||
{
|
||||
$validator = $this->createValidator();
|
||||
$validator->validate(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testConstructWithInvalidSchema()
|
||||
{
|
||||
new ManifestValidator(new JsonSchemaValidator(), array(), self::$DI['app']['phraseanet.version']);
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$validator = ManifestValidator::create(self::$DI['app']);
|
||||
|
||||
$this->assertInstanceOf('Alchemy\Phrasea\Plugin\Schema\ManifestValidator', $validator);
|
||||
}
|
||||
|
||||
private function createValidator()
|
||||
{
|
||||
$schema = json_decode($this->getSchema());
|
||||
|
||||
return new ManifestValidator(new JsonSchemaValidator(), $schema, self::$DI['app']['phraseanet.version']);
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Plugin\Schema;
|
||||
|
||||
use Alchemy\Phrasea\Plugin\Schema\PluginValidator;
|
||||
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
|
||||
use JsonSchema\Validator as JsonValidator;
|
||||
use Alchemy\Tests\Phrasea\Plugin\PluginTestCase;
|
||||
|
||||
class PluginValidatorTest extends PluginTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideInvalidPluginDirs
|
||||
* @expectedException Alchemy\Phrasea\Plugin\Exception\PluginValidationException
|
||||
*/
|
||||
public function testValidateInvalidPlugin($directory)
|
||||
{
|
||||
$validator = new PluginValidator($this->createManifestValidator());
|
||||
$validator->validatePlugin($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePluginDirs
|
||||
*/
|
||||
public function testValidatePlugin($directory)
|
||||
{
|
||||
$validator = new PluginValidator($this->createManifestValidator());
|
||||
$validator->validatePlugin($directory);
|
||||
}
|
||||
|
||||
public function providePluginDirs()
|
||||
{
|
||||
return array(
|
||||
array(__DIR__ . '/../Fixtures/PluginDir/TestPlugin'),
|
||||
);
|
||||
}
|
||||
|
||||
public function provideInvalidPluginDirs()
|
||||
{
|
||||
return array(
|
||||
array(__DIR__ . '/../Fixtures/WrongPlugins/TestPluginInvalidManifest'),
|
||||
array(__DIR__ . '/../Fixtures/WrongPlugins/TestPluginMissingComposer'),
|
||||
array(__DIR__ . '/../Fixtures/WrongPlugins/TestPluginMissingManifest'),
|
||||
array(__DIR__ . '/../Fixtures/WrongPlugins/TestPluginWrongManifest'),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
|
||||
$loader = require __DIR__ . '/../vendor/autoload.php';
|
||||
$loader = require __DIR__ . '/../lib/autoload.php';
|
||||
$loader->add('Alchemy\\Tests', __DIR__);
|
||||
$loader->add('', __DIR__ . "/classes");
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
|
||||
* @link www.phraseanet.com
|
||||
*/
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../lib/autoload.php';
|
||||
|
||||
$app = require __DIR__ . '/../lib/Alchemy/Phrasea/Application/Api.php';
|
||||
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
|
||||
* @link www.phraseanet.com
|
||||
*/
|
||||
require_once __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . "/../lib/autoload.php";
|
||||
|
||||
$environment = 'prod';
|
||||
$app = require __DIR__ . '/../lib/Alchemy/Phrasea/Application/Root.php';
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
|
||||
* @link www.phraseanet.com
|
||||
*/
|
||||
require_once __DIR__ . "/../vendor/autoload.php";
|
||||
require_once __DIR__ . "/../lib/autoload.php";
|
||||
|
||||
$environment = 'dev';
|
||||
$app = require __DIR__ . '/../lib/Alchemy/Phrasea/Application/Root.php';
|
||||
|
Reference in New Issue
Block a user