Introduce configuration-tester

This commit is contained in:
Romain Neutron
2013-04-17 21:19:55 +02:00
parent 1b82dfb1da
commit 5a80e9dc22
36 changed files with 1810 additions and 331 deletions

View File

@@ -20,6 +20,7 @@ use Alchemy\Phrasea\Core\Version;
use Alchemy\Phrasea\Command\UpgradeDBDatas;
use Alchemy\Phrasea\Command\Setup\Install;
use Alchemy\Phrasea\CLI;
use Alchemy\Phrasea\Command\Setup\CheckEnvironment;
require_once __DIR__ . '/../vendor/autoload.php';
@@ -59,7 +60,7 @@ try {
$app->command(new UpgradeDBDatas('system:upgrade-datas'));
}
$app->command(new \module_console_systemConfigCheck('check:system'));
$app->command(new CheckEnvironment('check:system'));
$app->command(new Install('system:install'));
$result_code = is_int($app->run()) ? : 1;

View File

@@ -11,16 +11,29 @@
namespace Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Controller\Setup\Installer;
use Alchemy\Phrasea\Controller\Utils\ConnectionTest;
use Alchemy\Phrasea\Controller\Utils\PathFileTest;
use Silex\Application as SilexApplication;
use Symfony\Component\HttpFoundation\Response;
return call_user_func(function() {
$app = new Application();
$app = new SilexApplication();
$app->get('/', function(PhraseaApplication $app) {
$app['debug'] = true;
$app['twig'] = $app->share(function (SilexApplication $app) {
$ld_path = array(__DIR__ . '/../../../../templates/web');
$loader = new \Twig_Loader_Filesystem($ld_path);
$twig = new \Twig_Environment($loader);
$twig->addExtension(new \Twig_Extensions_Extension_I18n());
return $twig;
});
$app->get('/', function(SilexApplication $app) {
if (!$app['phraseanet.configuration-tester']->isBlank()) {
return $app->redirect('/login/');
}

View File

@@ -0,0 +1,91 @@
<?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\Setup;
use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Setup\Installer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Console\Helper\DialogHelper;
use Alchemy\Phrasea\Setup\Requirements\BinariesRequirements;
use Alchemy\Phrasea\Setup\Requirements\FilesystemRequirements;
use Alchemy\Phrasea\Setup\Requirements\LocalesRequirements;
use Alchemy\Phrasea\Setup\Requirements\PhraseaRequirements;
use Alchemy\Phrasea\Setup\Requirements\PhpRequirements;
use Alchemy\Phrasea\Setup\Requirements\SystemRequirements;
class CheckEnvironment extends Command
{
public function __construct($name = null)
{
parent::__construct($name);
$this
->setDescription("Check environment");
return $this;
}
/**
* {@inheritdoc}
*/
protected function doExecute(InputInterface $input, OutputInterface $output)
{
foreach(array(
new BinariesRequirements(),
new FilesystemRequirements(),
new LocalesRequirements(),
new PhraseaRequirements(),
new PhpRequirements(),
new SystemRequirements(),
) as $collection) {
$output->writeln('');
$output->writeln($collection->getName() . ' requirements : ');
$output->writeln('');
foreach ($collection->getRequirements() as $requirement) {
$result = $requirement->isFulfilled() ? '<info>OK </info>' : ($requirement->isOptional() ? '<comment>WARNING</comment> ' : '<error>ERROR</error> ');
$output->write(' ' . $result);
$output->writeln($requirement->getTestMessage());
if (!$requirement->isFulfilled()) {
$output->writeln(" " . $requirement->getHelpText());
$output->writeln('');
}
}
$output->writeln('');
$output->writeln($collection->getName() . ' recommendations : ');
$output->writeln('');
foreach ($collection->getRecommendations() as $requirement) {
$result = $requirement->isFulfilled() ? '<info>OK </info>' : ($requirement->isOptional() ? '<comment>WARNING</comment> ' : '<error>ERROR</error> ');
$output->write(' ' . $result);
$output->writeln($requirement->getTestMessage());
if (!$requirement->isFulfilled()) {
$output->writeln(" " . $requirement->getHelpText());
$output->writeln('');
}
}
}
return;
}
}

View File

@@ -140,15 +140,6 @@ class Dashboard implements ControllerProviderInterface
'cache_flushed' => $request->query->get('flush_cache') === 'ok',
'admins' => \User_Adapter::get_sys_admins($app),
'email_status' => $emailStatus,
'search_engine_status' => $app['phraseanet.SE']->getStatus(),
'php_version_constraints' => \setup::check_php_version(),
'writability_constraints' => \setup::check_writability($app['phraseanet.registry']),
'binaries_constraints' => \setup::check_binaries($app['phraseanet.registry']),
'php_extension_constraints' => \setup::check_php_extension(),
'cache_constraints' => \setup::check_cache_server(),
'phrasea_constraints' => \setup::check_phrasea(),
'cache_opcode_constraints' => \setup::check_cache_opcode(),
'php_configuration_constraints' => \setup::check_php_configuration(),
);
return $app['twig']->render('admin/dashboard.html.twig', $parameters);

View File

@@ -38,64 +38,62 @@ class Installer implements ControllerProviderInterface
public function rootInstaller(Application $app, Request $request)
{
$php_constraint = \setup::check_php_version();
$writability_constraints = \setup::check_writability(new \Setup_Registry());
$extension_constraints = \setup::check_php_extension();
$opcode_constraints = \setup::check_cache_opcode();
$php_conf_constraints = \setup::check_php_configuration();
$locales_constraints = \setup::check_system_locales($app);
// $php_constraint = \setup::check_php_version();
// $writability_constraints = \setup::check_writability(new \Setup_Registry());
// $extension_constraints = \setup::check_php_extension();
// $opcode_constraints = \setup::check_cache_opcode();
// $php_conf_constraints = \setup::check_php_configuration();
// $locales_constraints = \setup::check_system_locales($app);
//
// $constraints_coll = array(
// 'php_constraint' => $php_constraint
// , 'writability_constraints' => $writability_constraints
// , 'extension_constraints' => $extension_constraints
// , 'opcode_constraints' => $opcode_constraints
// , 'php_conf_constraints' => $php_conf_constraints
// , 'locales_constraints' => $locales_constraints
// );
$constraints_coll = array(
'php_constraint' => $php_constraint
, 'writability_constraints' => $writability_constraints
, 'extension_constraints' => $extension_constraints
, 'opcode_constraints' => $opcode_constraints
, 'php_conf_constraints' => $php_conf_constraints
, 'locales_constraints' => $locales_constraints
new \Alchemy\Phrasea\Setup\Requirements\BinariesRequirements(),
new \Alchemy\Phrasea\Setup\Requirements\FilesystemRequirements(),
new \Alchemy\Phrasea\Setup\Requirements\LocalesRequirements(),
new \Alchemy\Phrasea\Setup\Requirements\PhpRequirements(),
new \Alchemy\Phrasea\Setup\Requirements\PhraseaRequirements(),
new \Alchemy\Phrasea\Setup\Requirements\SystemRequirements(),
);
$redirect = true;
// $redirect = true;
foreach ($constraints_coll as $key => $constraints) {
$unset = true;
foreach ($constraints as $constraint) {
if (!$constraint->is_ok() && $constraint->is_blocker())
$redirect = $unset = false;
}
if ($unset === true) {
unset($constraints_coll[$key]);
}
}
// foreach ($constraints_coll as $key => $constraints) {
// $unset = true;
// foreach ($constraints as $constraint) {
// if (!$constraint->is_ok() && $constraint->is_blocker())
// $redirect = $unset = false;
// }
// if ($unset === true) {
// unset($constraints_coll[$key]);
// }
// }
if ($redirect) {
return $app->redirect('/setup/installer/step2/');
}
// if ($redirect) {
// return $app->redirect('/setup/installer/step2/');
// }
$app['twig.loader.filesystem']->setPaths(array(
__DIR__ . '/../../../../../templates/web'
// $app['twig.loader.filesystem']->setPaths(array(
// __DIR__ . '/../../../../../templates/web'
// ));
return $app['twig']->render('/setup/index.html.twig', array(
'locale' => $app['locale'],
'available_locales' => $app->getAvailableLanguages(),
'current_servername' => $request->getScheme() . '://' . $request->getHttpHost() . '/',
'constraints' => $constraints_coll,
));
return $app['twig']->render(
'/setup/index.html.twig'
, array_merge($constraints_coll, array(
'locale' => $app['locale']
, 'available_locales' => $app->getAvailableLanguages()
, 'version_number' => $app['phraseanet.version']->getNumber()
, 'version_name' => $app['phraseanet.version']->getName()
, 'current_servername' => $request->getScheme() . '://' . $request->getHttpHost() . '/'
))
);
}
public function getInstallForm(Application $app, Request $request)
{
\phrasea::use_i18n($app['locale']);
$ld_path = array(__DIR__ . '/../../../../../templates/web');
$loader = new \Twig_Loader_Filesystem($ld_path);
$twig = new \Twig_Environment($loader);
$twig->addExtension(new \Twig_Extensions_Extension_I18n());
$warnings = array();
$php_constraint = \setup::check_php_version();
@@ -127,7 +125,7 @@ class Installer implements ControllerProviderInterface
$warnings[] = _('It is not recommended to install Phraseanet without HTTPS support');
}
return $twig->render(
return $app['twig']->render(
'/setup/step2.html.twig'
, array(
'locale' => $app['locale']

View File

@@ -12,17 +12,28 @@
namespace Alchemy\Phrasea\Setup;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\System\ProbeInterface as SystemProbeInterface;
use Alchemy\Phrasea\Setup\Version\Probe\Probe31;
use Alchemy\Phrasea\Setup\Version\Probe\Probe35;
use Alchemy\Phrasea\Setup\Version\Probe\ProbeInterface as VersionProbeInterface;
use Alchemy\Phrasea\Setup\Probe\BinariesProbe;
use Alchemy\Phrasea\Setup\Probe\CacheServerProbe;
use Alchemy\Phrasea\Setup\Probe\OpcodeCacheProbe;
use Alchemy\Phrasea\Setup\Probe\FilesystemProbe;
use Alchemy\Phrasea\Setup\Probe\LocalesProbe;
use Alchemy\Phrasea\Setup\Probe\PhpProbe;
use Alchemy\Phrasea\Setup\Probe\PhraseaProbe;
use Alchemy\Phrasea\Setup\Probe\SearchEngineProbe;
use Alchemy\Phrasea\Setup\Probe\SystemProbe;
class ConfigurationTester
{
private $app;
private $probes;
private $requirements;
private $versionProbes;
const PROD_ENV = 'prod';
const DEV_ENV = 'dev';
public function __construct(Application $app)
{
$this->app = $app;
@@ -33,9 +44,25 @@ class ConfigurationTester
);
}
public function registerProbe(SystemProbeInterface $probe)
public function getRequirements()
{
$this->probes[] = $probe;
if ($this->requirements) {
return $this->requirements;
}
$this->requirements = array(
BinariesProbe::create($this->app),
CacheServerProbe::create($this->app),
OpcodeCacheProbe::create($this->app),
FilesystemProbe::create($this->app),
LocalesProbe::create($this->app),
PhpProbe::create($this->app),
PhraseaProbe::create($this->app),
SearchEngineProbe::create($this->app),
SystemProbe::create($this->app),
);
return $this->requirements;
}
public function registerVersionProbe(VersionProbeInterface $probe)
@@ -96,6 +123,7 @@ class ConfigurationTester
}
/**
* Returns true if a major migration script can be executed
*
* @return type
*/

View File

@@ -0,0 +1,38 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Requirements\BinariesRequirements;
class BinariesProbe extends BinariesRequirements implements ProbeInterface
{
const REQUIRED_PHP_VERSION = '5.3.3';
public function __construct(\registryInterface $registry)
{
parent::__construct(array_filter(array(
'php_binary' => $registry->get('php_binary'),
'convert_binary' => $registry->get('convert_binary'),
'pdf2swf_binary' => $registry->get('pdf2swf_binary'),
'unoconv_binary' => $registry->get('unoconv_binary'),
'swf_extract_binary' => $registry->get('swf_extract_binary'),
'swf_render_binary' => $registry->get('swf_render_binary'),
'mp4box_binary' => $registry->get('mp4box_binary'),
'pdftotext_binary' => $registry->get('pdftotext_binary'),
'composite_binary' => $registry->get('composite_binary'),
'ffmpeg_binary' => $registry->get('ffmpeg_binary'),
'ffprobe_binary' => $registry->get('ffprobe_binary'),
)));
}
/**
* {@inheritdoc}
*
* @return BinariesProbe
*/
public static function create(Application $app)
{
return new static($app['phraseanet.registry']);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Requirements\FilesystemRequirements;
class FilesystemProbe extends FilesystemRequirements implements ProbeInterface
{
public function __construct(\registryInterface $registry)
{
parent::__construct();
$baseDir = realpath(__DIR__ . '/../../../../../');
$paths = array(
$baseDir . '/config/config.yml',
$baseDir . '/config/connexions.yml',
$baseDir . '/config/services.yml',
$baseDir . '/config/binaries.yml',
);
foreach ($paths as $path) {
$this->addRecommendation(
"00" === substr(sprintf('%o', fileperms($path)), -2),
"$path should not be readable or writeable for other users, current mode is (".substr(sprintf('%o', fileperms($path)), -4).")",
"Change the permissions of the \"<strong>$path</strong>\" file to 0600"
);
}
$path = array();
if ($registry->is_set('GV_base_datapath_noweb')) {
$paths[] = $registry->get('GV_base_datapath_noweb');
}
foreach ($paths as $path) {
$this->addRequirement(
is_writable($path),
"$path directory must be writable",
"Change the permissions of the \"<strong>$path</strong>\" directory so that the web server can write into it."
);
}
}
/**
* {@inheritdoc}
*
* @return FilesystemProbe
*/
public static function create(Application $app)
{
return new static($app['phraseanet.registry']);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Requirements\LocalesRequirements;
class LocalesProbe extends LocalesRequirements implements ProbeInterface
{
public function __construct($locale)
{
parent::__construct($locale);
}
/**
* {@inheritdoc}
*
* @return LocalesProbe
*/
public static function create(Application $app)
{
return new static($app['locale']);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Requirements\PhpRequirements;
class PhpProbe extends PhpRequirements implements ProbeInterface
{
/**
* {@inheritdoc}
*
* @return SystemProbe
*/
public static function create(Application $app)
{
return new static();
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Requirements\PhraseaRequirements;
class PhraseaProbe extends PhraseaRequirements implements ProbeInterface
{
/**
* {@inheritdoc}
*
* @return PhraseaProbe
*/
public static function create(Application $app)
{
return new static();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\System\RequirementCollectionInterface;
interface ProbeInterface extends RequirementCollectionInterface
{
/**
* @param Application $app
*/
public static function create(Application $app);
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
class SearchEngineProbe extends RequirementCollection implements ProbeInterface
{
public function __construct(SearchEngineInterface $searchEngine)
{
$this->setName('Search Engine');
foreach ($searchEngine->getStatus() as $infos)
{
$this->addInformation($infos[0], $infos[1]);
}
}
public static function create(Application $app)
{
return new static($app['phraseanet.SE']);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Alchemy\Phrasea\Setup\Probe;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Setup\Requirements\SystemRequirements;
class SystemProbe extends SystemRequirements implements ProbeInterface
{
/**
* {@inheritdoc}
*
* @return SystemProbe
*/
public static function create(Application $app)
{
return new static();
}
}

View File

@@ -0,0 +1,213 @@
<?php
namespace Alchemy\Phrasea\Setup\Requirements;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
use Symfony\Component\Process\ExecutableFinder;
class BinariesRequirements extends RequirementCollection
{
const REQUIRED_PHP_VERSION = '5.3.3';
public function __construct($binaries = array())
{
$this->setName('Binaries');
$finder = new ExecutableFinder();
$phpCLI = isset($binaries['php_binary']) ? $binaries['php_binary'] : $finder->find('php');
$this->addRequirement(
null !== $phpCLI && is_executable($phpCLI),
'PHP CLI is required to run Phraseanet task manager',
'Please reinstall PHP with CLI support'
);
$indexer = $finder->find('phraseanet_indexer');
$this->addRecommendation(
null !== $indexer && is_executable($indexer),
'Phraseanet Indexer is required to use Phrasea search-engine',
'Please install latest Phraseanet Indexer (https://github.com/alchemy-fr/Phraseanet-Indexer)'
);
$convert = isset($binaries['convert_binary']) ? $binaries['convert_binary'] : $finder->find('convert');
$this->addRequirement(
null !== $convert && is_executable($convert),
'ImageMagick Convert is required',
'Please install ImageMagick'
);
if (null !== $convert) {
$output = null;
exec($convert . ' --version', $output);
$data = sscanf($output[0], 'Version: ImageMagick %d.%d.%d');
$version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]);
$this->addRequirement(
version_compare('6.2.9', $version, '<'),
'Convert version 6.2.9 or higher is required ('.$version.' provided)',
'Please update to a more recent version'
);
}
$composite = isset($binaries['composite_binary']) ? $binaries['composite_binary'] : $finder->find('composite');
$this->addRequirement(
null !== $composite && is_executable($composite),
'ImageMagick Composite is required',
'Please install ImageMagick'
);
if (null !== $composite) {
$output = null;
exec($composite . ' --version', $output);
$data = sscanf($output[0], 'Version: ImageMagick %d.%d.%d');
$version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]);
$this->addRequirement(
version_compare('6.2.9', $version, '<'),
'Composite version 6.2.9 or higher is required ('.$version.' provided)',
'Please update to a more recent version.'
);
}
$pdf2swf = isset($binaries['pdf2swf_binary']) ? $binaries['pdf2swf_binary'] : $finder->find('pdf2swf');
$this->addRecommendation(
null !== $pdf2swf && is_executable($pdf2swf),
'SWFTools are required for documents (Word, Excel, PDF, etc...) support',
'Please install SWFTools (http://www.swftools.org/)'
);
if (null !== $pdf2swf) {
$output = null;
exec($pdf2swf . ' --version', $output);
$data = sscanf($output[0], 'pdf2swf - part of swftools %d.%d.%d');
$version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]);
$this->addRecommendation(
version_compare('0.9.0', $version, '<='),
'SWFTools (pdf2swf) version 0.9.0 or higher is required ('.$version.' provided)',
'Please update to a more recent version.'
);
}
$unoconv = isset($binaries['unoconv_binary']) ? $binaries['unoconv_binary'] : $finder->find('unoconv');
$this->addRecommendation(
null !== $unoconv && is_executable($unoconv),
'Unoconv is required for documents (Word, Excel, etc...) support',
'Please install Unoconv'
);
if (null !== $unoconv) {
$output = null;
exec($unoconv . ' --version', $output);
$data = sscanf($output[0], 'unoconv %d.%d');
$version = sprintf('%d.%d', $data[0], $data[1]);
$this->addRecommendation(
version_compare('0.5', $version, '<='),
'Unoconv version 0.5 or higher is required ('.$version.' provided)',
'Please update to a more recent version.'
);
}
$swfextract = isset($binaries['swf_extract_binary']) ? $binaries['swf_extract_binary'] : $finder->find('swfextract');
$this->addRecommendation(
null !== $swfextract && is_executable($swfextract),
'SWFTools (swfextract) are required for flash files support',
'Please install SWFTools (http://www.swftools.org/)'
);
if (null !== $swfextract) {
$output = null;
exec($swfextract . ' --version', $output);
$data = sscanf($output[0], 'swfextract - part of swftools %d.%d.%d');
$version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]);
$this->addRecommendation(
version_compare('0.9.0', $version, '<='),
'SWFTools (swfextract) version 0.9.0 or higher is required ('.$version.' provided)',
'Please update to a more recent version.'
);
}
$swfrender = isset($binaries['swf_render_binary']) ? $binaries['swf_render_binary'] : $finder->find('swfrender');
$this->addRecommendation(
null !== $swfrender && is_executable($swfrender),
'SWFTools (swfrender) are required for flash files support',
'Please install SWFTools (http://www.swftools.org/)'
);
if (null !== $swfrender) {
$output = null;
exec($swfrender . ' --version', $output);
$data = sscanf($output[0], 'swfrender - part of swftools %d.%d.%d');
$version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]);
$this->addRecommendation(
version_compare('0.9.0', $version, '<='),
'SWFTools (swfrender) version 0.9.0 or higher is required ('.$version.' provided)',
'Please update to a more recent version.'
);
}
$mp4box = isset($binaries['mp4box_binary']) ? $binaries['mp4box_binary'] : $finder->find('MP4Box');
$this->addRecommendation(
null !== $mp4box && is_executable($mp4box),
'MP4Box is required for video support',
'Please install MP4Box'
);
if (null !== $mp4box) {
$output = null;
exec($mp4box . ' -version', $output);
$data = sscanf($output[0], 'MP4Box - GPAC version %d.%d.%d');
$version = sprintf('%d.%d.%d', $data[0], $data[1], $data[2]);
$this->addRecommendation(
version_compare('0.4.0', $version, '<='),
'MP4Box version 0.4.0 or higher is required ('.$version.' provided)',
'Please update to a more recent version.'
);
}
$pdftotext = isset($binaries['pdftotext_binary']) ? $binaries['pdftotext_binary'] : $finder->find('pdftotext');
$this->addRecommendation(
null !== $pdftotext && is_executable($pdftotext),
'XPDF is required for PDF indexation',
'Please install XPDF'
);
$ffmpeg = isset($binaries['ffmpeg_binary']) ? $binaries['ffmpeg_binary'] : $finder->find('ffmpeg');
if (null === $ffmpeg) {
$ffmpeg = $finder->find('avconv');
}
$this->addRecommendation(
null !== $ffmpeg && is_executable($ffmpeg),
'FFMpeg (or libav-tools) is required for Video processing',
'Please install FFMpeg (or libav-tools)'
);
$ffprobe = isset($binaries['ffprobe_binary']) ? $binaries['ffprobe_binary'] : $finder->find('ffprobe');
if (null === $ffprobe) {
$ffprobe = $finder->find('avprobe');
}
$this->addRecommendation(
null !== $ffprobe && is_executable($ffprobe),
'FFProbe (or avprobe) is required for Video processing',
'Please install FFProbe (or avprobe)'
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Alchemy\Phrasea\Setup\Requirements;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
class FilesystemRequirements extends RequirementCollection
{
public function __construct()
{
$baseDir = realpath(__DIR__ . '/../../../../../');
$this->setName('Filesystem');
$paths = array(
$baseDir . '/config',
$baseDir . '/config/stamp',
$baseDir . '/config/status',
$baseDir . '/config/minilogos',
$baseDir . '/config/templates',
$baseDir . '/config/topics',
$baseDir . '/config/wm',
$baseDir . '/logs',
$baseDir . '/tmp',
$baseDir . '/www/custom',
$baseDir . '/tmp/locks',
$baseDir . '/tmp/cache_twig',
$baseDir . '/tmp/cache_minify',
$baseDir . '/tmp/lazaret',
$baseDir . '/tmp/desc_tmp',
$baseDir . '/tmp/download',
$baseDir . '/tmp/batches'
);
foreach ($paths as $path) {
$this->addRequirement(
is_writable($path),
"$path directory must be writable",
"Change the permissions of the \"<strong>$path</strong>\" directory so that the web server can write into it."
);
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Alchemy\Phrasea\Setup\Requirements;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
use Alchemy\Phrasea\Application as PhraseaApplication;
class LocalesRequirements extends RequirementCollection
{
public function __construct($locale = 'en_GB')
{
$this->setName('Locales');
$this->addRequirement(
class_exists('Locale'),
'intl extension should be available',
'Install and enable the <strong>intl</strong> extension (used for validators).'
);
if (function_exists('_')) {
foreach (PhraseaApplication::getAvailableLanguages() as $code => $language_name){
\phrasea::use_i18n($code, 'test');
$this->addRecommendation(
'test' === _('test::test'),
sprintf('Locale %s (%s) should be supported', $language_name, $code),
'Install support for locale <strong>' . $code . '</strong> (' . $language_name . ').'
);
\phrasea::use_i18n($locale);
}
}
if (class_exists('Collator')) {
$this->addRecommendation(
null !== new \Collator('fr_FR'),
'intl extension should be correctly configured',
'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.'
);
}
if (class_exists('Locale')) {
if (defined('INTL_ICU_VERSION')) {
$version = INTL_ICU_VERSION;
} else {
$reflector = new ReflectionExtension('intl');
ob_start();
$reflector->info();
$output = strip_tags(ob_get_clean());
preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches);
$version = $matches[1];
}
$this->addRecommendation(
version_compare($version, '4.0', '>='),
'intl ICU version should be at least 4+',
'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).'
);
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Alchemy\Phrasea\Setup\Requirements;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
class PhpRequirements extends RequirementCollection
{
public function __construct()
{
$this->setName('PHP');
$this->addPhpIniRequirement(
'date.timezone', true, false,
'date.timezone setting must be set',
'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).'
);
$this->addPhpIniRequirement('detect_unicode', false);
if (extension_loaded('suhosin')) {
$this->addPhpIniRequirement(
'suhosin.executor.include.whitelist',
create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'),
false,
'suhosin.executor.include.whitelist must be configured correctly in php.ini',
'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.'
);
}
if (extension_loaded('xdebug')) {
$this->addPhpIniRequirement(
'xdebug.show_exception_trace', false, true
);
$this->addPhpIniRequirement(
'xdebug.scream', false, true
);
$this->addPhpIniRecommendation(
'xdebug.max_nesting_level',
create_function('$cfgValue', 'return $cfgValue > 100;'),
true,
'xdebug.max_nesting_level should be above 100 in php.ini',
'Set "<strong>xdebug.max_nesting_level</strong>" to e.g. "<strong>250</strong>" in php.ini<a href="#phpini">*</a> to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.'
);
}
$this->addPhpIniRequirement('safe_mode', false, true);
$this->addPhpIniRequirement('file_uploads', true, true);
$this->addPhpIniRequirement('session.cache_limiter', '');
$this->addPhpIniRequirement('default_charset', 'UTF-8', true, 'Default charset should be UTF-8', 'Set default_charset to UTF-8 in php.ini');
$this->addPhpIniRequirement('magic_quotes_gpc', false, true);
$this->addPhpIniRequirement('magic_quotes_runtime', false, true);
$this->addPhpIniRecommendation('short_open_tag', false);
$this->addPhpIniRecommendation('register_globals', false, true);
$this->addPhpIniRecommendation('session.auto_start', false);
$this->addPhpIniRecommendation('display_errors', false, true);
$this->addPhpIniRecommendation('display_startup_errors', false, true);
$this->addPhpIniRecommendation('allow_url_fopen', true, true);
$this->addPhpIniRecommendation('session.hash_bits_per_character', '6', true, 'session.hash_bits_per_character should be at least 6', 'Set session.hash_bits_per_character to 6 in php.ini');
$this->addPhpIniRecommendation('session.hash_function', true, true);
$this->addPhpIniRecommendation('session.use_only_cookies', true, true);
$this->addPhpIniRecommendation('session.use_cookies', true, true);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Alchemy\Phrasea\Setup\Requirements;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
class PhraseaRequirements extends RequirementCollection
{
public function __construct()
{
$this->setName('Phrasea');
$this->addRecommendation(
function_exists('phrasea_fetch_results'),
'phrasea extension is required to use Phrasea search-engine',
'Install and enable the <strong>phrasea</strong> extension to enable Phrasea search-engine (https://github.com/alchemy-fr/Phraseanet-Extension).'
);
if (function_exists('phrasea_fetch_results')) {
$infos = phrasea_info();
$this->addRequirement(
version_compare($infos['version'], '1.21.0.1', '>='),
'phrasea extension version 1.21.0.1 is required (version ' . $infos['version'] . ' installed)',
'Update <strong>phrasea</strong> extension to the latest stable (https://github.com/alchemy-fr/Phraseanet-Extension).'
);
$this->addRequirement(
true === $infos['temp_writable'],
'phrasea extension should be able to write in its temporary directory (current is ' . $infos['temp_dir'] . ')',
'Change directory <strong>' . $infos['temp_dir'] . '</strong> mode so phrasea extension could write to it'
);
}
}
}

View File

@@ -0,0 +1,331 @@
<?php
namespace Alchemy\Phrasea\Setup\Requirements;
use Alchemy\Phrasea\Setup\System\RequirementCollection;
use Alchemy\Phrasea\Application as PhraseaApplication;
class SystemRequirements extends RequirementCollection
{
const REQUIRED_PHP_VERSION = '5.3.3';
public function __construct()
{
$installedPhpVersion = phpversion();
$baseDir = realpath(__DIR__ . '/../../../../../');
$this->setName('System');
$this->addRequirement(
version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>='),
sprintf('PHP version must be at least %s (%s installed)', self::REQUIRED_PHP_VERSION, $installedPhpVersion),
sprintf('You are running PHP version "<strong>%s</strong>", but Phraseanet needs at least PHP "<strong>%s</strong>" to run.
Before using Phraseanet, upgrade your PHP installation, preferably to the latest version.',
$installedPhpVersion, self::REQUIRED_PHP_VERSION),
sprintf('Install PHP %s or newer (installed version is %s)', self::REQUIRED_PHP_VERSION, $installedPhpVersion)
);
$this->addRequirement(
version_compare($installedPhpVersion, '5.3.16', '!='),
'PHP version must not be 5.3.16 as Phraseanet won\'t work properly with it',
'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)'
);
$this->addRequirement(
is_dir($baseDir.'/vendor/composer'),
'Vendor libraries must be installed',
'Vendor libraries are missing. Install composer following instructions from <a href="http://getcomposer.org/">http://getcomposer.org/</a>. ' .
'Then run "<strong>php composer.phar install</strong>" to install them.'
);
$this->addPhpIniRequirement(
'date.timezone', true, false,
'date.timezone setting must be set',
'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).'
);
if (version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>=')) {
$timezones = array();
foreach (\DateTimeZone::listAbbreviations() as $abbreviations) {
foreach ($abbreviations as $abbreviation) {
$timezones[$abbreviation['timezone_id']] = true;
}
}
$this->addRequirement(
isset($timezones[date_default_timezone_get()]),
sprintf('Configured default timezone "%s" must be supported by your installation of PHP', date_default_timezone_get()),
'Your default timezone is not supported by PHP. Check for typos in your <strong>php.ini</strong> file and have a look at the list of deprecated timezones at <a href="http://php.net/manual/en/timezones.others.php">http://php.net/manual/en/timezones.others.php</a>.'
);
}
$this->addRequirement(
function_exists('json_encode'),
'json_encode() must be available',
'Install and enable the <strong>JSON</strong> extension.'
);
$this->addRequirement(
function_exists('session_start'),
'session_start() must be available',
'Install and enable the <strong>session</strong> extension.'
);
$this->addRequirement(
function_exists('ctype_alpha'),
'ctype_alpha() must be available',
'Install and enable the <strong>ctype</strong> extension.'
);
$this->addRequirement(
function_exists('token_get_all'),
'token_get_all() must be available',
'Install and enable the <strong>Tokenizer</strong> extension.'
);
$this->addRequirement(
function_exists('simplexml_import_dom'),
'simplexml_import_dom() must be available',
'Install and enable the <strong>SimpleXML</strong> extension.'
);
if (function_exists('apc_store') && ini_get('apc.enabled')) {
if (version_compare($installedPhpVersion, '5.4.0', '>=')) {
$this->addRequirement(
version_compare(phpversion('apc'), '3.1.13', '>='),
'APC version must be at least 3.1.13 when using PHP 5.4',
'Upgrade your <strong>APC</strong> extension (3.1.13+).'
);
} else {
$this->addRequirement(
version_compare(phpversion('apc'), '3.0.17', '>='),
'APC version must be at least 3.0.17',
'Upgrade your <strong>APC</strong> extension (3.0.17+).'
);
}
}
$this->addPhpIniRequirement('detect_unicode', false);
if (extension_loaded('suhosin')) {
$this->addPhpIniRequirement(
'suhosin.executor.include.whitelist',
create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'),
false,
'suhosin.executor.include.whitelist must be configured correctly in php.ini',
'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.'
);
}
if (extension_loaded('xdebug')) {
$this->addPhpIniRequirement(
'xdebug.show_exception_trace', false, true
);
$this->addPhpIniRequirement(
'xdebug.scream', false, true
);
$this->addPhpIniRecommendation(
'xdebug.max_nesting_level',
create_function('$cfgValue', 'return $cfgValue > 100;'),
true,
'xdebug.max_nesting_level should be above 100 in php.ini',
'Set "<strong>xdebug.max_nesting_level</strong>" to e.g. "<strong>250</strong>" in php.ini<a href="#phpini">*</a> to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.'
);
}
$pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null;
$this->addRequirement(
null !== $pcreVersion,
'PCRE extension must be available',
'Install the <strong>PCRE</strong> extension (version 8.0+).'
);
$this->addRecommendation(
version_compare($installedPhpVersion, '5.3.4', '>='),
'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions',
'Your project might malfunction randomly due to PHP bug #52083 ("Notice: Trying to get property of non-object"). Install PHP 5.3.4 or newer.'
);
$this->addRecommendation(
version_compare($installedPhpVersion, '5.3.8', '>='),
'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156',
'Install PHP 5.3.8 or newer if your project uses annotations.'
);
$this->addRecommendation(
version_compare($installedPhpVersion, '5.4.0', '!='),
'You should not use PHP 5.4.0 due to the PHP bug #61453',
'Your project might not work properly due to the PHP bug #61453 ("Cannot dump definitions which have method calls"). Install PHP 5.4.1 or newer.'
);
if (null !== $pcreVersion) {
$this->addRecommendation(
$pcreVersion >= 8.0,
sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion),
'<strong>PCRE 8.0+</strong> is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Phraseanet probably works anyway but it is recommended to upgrade your PCRE extension.'
);
}
$this->addRequirement(
class_exists('DomDocument'),
'PHP-XML module should be installed',
'Install and enable the <strong>PHP-XML</strong> module.'
);
$this->addRequirement(
function_exists('mb_strlen'),
'mb_strlen() should be available',
'Install and enable the <strong>mbstring</strong> extension.'
);
$this->addRequirement(
function_exists('iconv'),
'iconv() should be available',
'Install and enable the <strong>iconv</strong> extension.'
);
$this->addRequirement(
function_exists('exif_read_data'),
'exif extension is required',
'Install and enable the <strong>exif</strong> extension to enable FTP exports.'
);
$this->addRequirement(
function_exists('curl_init'),
'curl extension is required',
'Install and enable the <strong>curl</strong> extension.'
);
$this->addRequirement(
function_exists('gd_info'),
'gd extension is required',
'Install and enable the <strong>gd</strong> extension.'
);
$this->addRequirement(
function_exists('hash_hmac'),
'hash extension is required',
'Install and enable the <strong>hash</strong> extension.'
);
if ('cli' === php_sapi_name() && !defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->addRecommendation(
function_exists('pcntl_fork'),
'pcntl extension is recommended in unix environments',
'Install and enable the <strong>pcntl</strong> extension to enable process fork.'
);
}
$this->addRequirement(
function_exists('proc_open'),
'proc_* functions are required',
'Enable the <strong>proc_c*</strong> functions.'
);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->addRecommendation(
function_exists('posix_uname'),
'Posix extension is recommended for task manager',
'Install and enable the <strong>posix</strong> extension to enable process fork.'
);
}
$this->addRequirement(
function_exists('socket_connect'),
'Socket extension is required for task manager',
'Install and enable the <strong>socket</strong> extension.'
);
$this->addRequirement(
class_exists('ZipArchive'),
'Zip extension is required for download',
'Install and enable the <strong>zip</strong> extension.'
);
$this->addRecommendation(
extension_loaded('twig'),
'Twig extension is strongly recommended in production',
'Install and enable the <strong>twig</strong> extension.'
);
$this->addRecommendation(
class_exists('Imagick') || class_exists('Gmagick'),
'Imagick or Gmagick extension is strongly recommended for image processing',
'Install and enable the <strong>gmagick</strong> or <strong>imagick</strong> extension.'
);
$this->addRecommendation(
class_exists('Gmagick'),
'Gmagick extension is required for video processing (animated thumbnail generation)',
'Install and enable the <strong>gmagick</strong> extension.'
);
$this->addRecommendation(
function_exists('finfo_open'),
'Fileinfo extension is recommended',
'Install and enable the <strong>fileinfo</strong> extension to enable file detection.'
);
$this->addRequirement(
function_exists('utf8_decode'),
'utf8_decode() should be available',
'Install and enable the <strong>XML</strong> extension.'
);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->addRecommendation(
function_exists('posix_isatty'),
'posix_isatty() should be available',
'Install and enable the <strong>php_posix</strong> extension (used to colorize the CLI output).'
);
}
$this->addRecommendation(
function_exists('ftp_fget'),
'ftp extension is required for FTP export',
'Install and enable the <strong>ftp</strong> extension to enable FTP exports.'
);
$accelerator =
(function_exists('apc_store') && ini_get('apc.enabled'))
||
function_exists('eaccelerator_put') && ini_get('eaccelerator.enable')
||
function_exists('xcache_set')
;
$this->addRecommendation(
$accelerator,
'a PHP accelerator should be installed',
'Install and enable a <strong>PHP accelerator</strong> like APC (highly recommended).'
);
$this->addPhpIniRecommendation('short_open_tag', false);
$this->addPhpIniRecommendation('magic_quotes_gpc', false, true);
$this->addPhpIniRecommendation('register_globals', false, true);
$this->addPhpIniRecommendation('session.auto_start', false);
$this->addRequirement(
class_exists('PDO'),
'PDO should be installed',
'Install <strong>PDO</strong> (mandatory for Doctrine).'
);
if (class_exists('PDO')) {
$drivers = \PDO::getAvailableDrivers();
$this->addRequirement(
in_array('mysql', $drivers),
sprintf('PDO should have MySQL driver installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'),
'Install <strong>PDO MySQL driver</strong>.'
);
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Alchemy\Phrasea\Setup\System;
class Information implements InformationInterface
{
private $name;
private $value;
public function __construct($name, $value)
{
$this->name = $name;
$this->value = $value;
}
public function getName()
{
return $this->name;
}
public function getValue()
{
return $this->value;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Alchemy\Phrasea\Setup\System;
interface InformationInterface
{
public function getName();
public function getValue();
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Alchemy\Phrasea\Setup\System;
/**
* @see https://github.com/sensio/SensioDistributionBundle/blob/master/Resources/skeleton/app/SymfonyRequirements.php
*
* Represents a PHP requirement in form of a php.ini configuration.
*
* @author Tobias Schultze <http://tobion.de>
*/
class PhpIniRequirement extends Requirement
{
/**
* Constructor that initializes the requirement.
*
* @param string $cfgName The configuration name used for ini_get()
* @param Boolean|callback $evaluation Either a Boolean indicating whether the configuration should evaluate to true or false,
or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
* @param Boolean $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
* @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a Boolean a default message is derived)
* @param string|null $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a Boolean a default help is derived)
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
* @param Boolean $optional Whether this is only an optional recommendation not a mandatory requirement
*/
public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false)
{
$cfgValue = ini_get($cfgName);
if (is_callable($evaluation)) {
if (null === $testMessage || null === $helpHtml) {
throw new InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.');
}
$fulfilled = call_user_func($evaluation, $cfgValue);
} else {
if (null === $testMessage) {
$testMessage = sprintf('%s %s be %s in php.ini',
$cfgName,
$optional ? 'should' : 'must',
$evaluation ? 'enabled' : 'disabled'
);
}
if (null === $helpHtml) {
$helpHtml = sprintf('Set <strong>%s</strong> to <strong>%s</strong> in php.ini<a href="#phpini">*</a>.',
$cfgName,
$evaluation ? 'on' : 'off'
);
}
$fulfilled = $evaluation == $cfgValue;
}
parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional);
}
}

View File

@@ -1,16 +0,0 @@
<?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\Setup\System;
interface ProbeInterface
{
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Alchemy\Phrasea\Setup\System;
/**
* @see https://github.com/sensio/SensioDistributionBundle/blob/master/Resources/skeleton/app/SymfonyRequirements.php
*
* Represents a single PHP requirement, e.g. an installed extension.
* It can be a mandatory requirement or an optional recommendation.
* There is a special subclass, named PhpIniRequirement, to check a php.ini configuration.
*
* @author Tobias Schultze <http://tobion.de>
*/
class Requirement implements RequirementInterface
{
private $fulfilled;
private $testMessage;
private $helpText;
private $helpHtml;
private $optional;
/**
* Constructor that initializes the requirement.
*
* @param Boolean $fulfilled Whether the requirement is fulfilled
* @param string $testMessage The message for testing the requirement
* @param string $helpHtml The help text formatted in HTML for resolving the problem
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
* @param Boolean $optional Whether this is only an optional recommendation not a mandatory requirement
*/
public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false)
{
$this->fulfilled = (Boolean) $fulfilled;
$this->testMessage = (string) $testMessage;
$this->helpHtml = (string) $helpHtml;
$this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText;
$this->optional = (Boolean) $optional;
}
/**
* {@inheritdoc}
*/
public function isFulfilled()
{
return $this->fulfilled;
}
/**
* {@inheritdoc}
*/
public function getTestMessage()
{
return $this->testMessage;
}
/**
* {@inheritdoc}
*/
public function getHelpText()
{
return $this->helpText;
}
/**
* {@inheritdoc}
*/
public function getHelpHtml()
{
return $this->helpHtml;
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return $this->optional;
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace Alchemy\Phrasea\Setup\System;
/**
* @see https://github.com/sensio/SensioDistributionBundle/blob/master/Resources/skeleton/app/SymfonyRequirements.php
*
* A RequirementCollection represents a set of Requirement instances.
*
* @author Tobias Schultze <http://tobion.de>
*/
class RequirementCollection implements RequirementCollectionInterface
{
private $requirements = array();
private $informations = array();
private $name;
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
public function addInformation($name, $value)
{
$this->informations[] = new Information($name, $value);
return $this;
}
public function getInformations()
{
return $this->informations;
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->requirements);
}
/**
* {@inheritdoc}
*/
public function add(Requirement $requirement)
{
$this->requirements[] = $requirement;
}
/**
* {@inheritdoc}
*/
public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null)
{
$this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false));
}
/**
* {@inheritdoc}
*/
public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null)
{
$this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true));
}
/**
* {@inheritdoc}
*/
public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
{
$this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false));
}
/**
* {@inheritdoc}
*/
public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
{
$this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true));
}
/**
* {@inheritdoc}
*/
public function addCollection(RequirementCollection $collection)
{
$this->requirements = array_merge($this->requirements, $collection->all());
}
/**
* {@inheritdoc}
*/
public function all()
{
return $this->requirements;
}
/**
* {@inheritdoc}
*/
public function getRequirements()
{
$array = array();
foreach ($this->requirements as $req) {
if (!$req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* {@inheritdoc}
*/
public function getFailedRequirements()
{
$array = array();
foreach ($this->requirements as $req) {
if (!$req->isFulfilled() && !$req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* {@inheritdoc}
*/
public function getRecommendations()
{
$array = array();
foreach ($this->requirements as $req) {
if ($req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* {@inheritdoc}
*/
public function getFailedRecommendations()
{
$array = array();
foreach ($this->requirements as $req) {
if (!$req->isFulfilled() && $req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* {@inheritdoc}
*/
public function hasPhpIniConfigIssue()
{
foreach ($this->requirements as $req) {
if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getPhpIniConfigPath()
{
return get_cfg_var('cfg_file_path');
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Alchemy\Phrasea\Setup\System;
/**
* @see https://github.com/sensio/SensioDistributionBundle/blob/master/Resources/skeleton/app/SymfonyRequirements.php
*
* A RequirementCollection represents a set of Requirement instances.
*
* @author Tobias Schultze <http://tobion.de>
*/
interface RequirementCollectionInterface extends \IteratorAggregate
{
/**
* Get the name of the requirement collection
*
* @return String
*/
public function getName();
/**
* Set a name to the requirement collection
*
* @param String $name
*
* @return RequirementCollectionInterface
*/
public function setName($name);
public function addInformation($name, $value);
public function getInformations();
/**
* Adds a Requirement.
*
* @param Requirement $requirement A Requirement instance
*/
public function add(Requirement $requirement);
/**
* Adds a mandatory requirement.
*
* @param Boolean $fulfilled Whether the requirement is fulfilled
* @param string $testMessage The message for testing the requirement
* @param string $helpHtml The help text formatted in HTML for resolving the problem
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null);
/**
* Adds an optional recommendation.
*
* @param Boolean $fulfilled Whether the recommendation is fulfilled
* @param string $testMessage The message for testing the recommendation
* @param string $helpHtml The help text formatted in HTML for resolving the problem
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null);
/**
* Adds a mandatory requirement in form of a php.ini configuration.
*
* @param string $cfgName The configuration name used for ini_get()
* @param Boolean|callback $evaluation Either a Boolean indicating whether the configuration should evaluate to true or false,
or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
* @param Boolean $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
* @param string $testMessage The message for testing the requirement (when null and $evaluation is a Boolean a default message is derived)
* @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a Boolean a default help is derived)
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null);
/**
* Adds an optional recommendation in form of a php.ini configuration.
*
* @param string $cfgName The configuration name used for ini_get()
* @param Boolean|callback $evaluation Either a Boolean indicating whether the configuration should evaluate to true or false,
or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
* @param Boolean $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
* @param string $testMessage The message for testing the requirement (when null and $evaluation is a Boolean a default message is derived)
* @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a Boolean a default help is derived)
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null);
/**
* Adds a requirement collection to the current set of requirements.
*
* @param RequirementCollection $collection A RequirementCollection instance
*/
public function addCollection(RequirementCollection $collection);
/**
* Returns both requirements and recommendations.
*
* @return array Array of Requirement instances
*/
public function all();
/**
* Returns all mandatory requirements.
*
* @return array Array of Requirement instances
*/
public function getRequirements();
/**
* Returns the mandatory requirements that were not met.
*
* @return array Array of Requirement instances
*/
public function getFailedRequirements();
/**
* Returns all optional recommmendations.
*
* @return array Array of Requirement instances
*/
public function getRecommendations();
/**
* Returns the recommendations that were not met.
*
* @return array Array of Requirement instances
*/
public function getFailedRecommendations();
/**
* Returns whether a php.ini configuration is not correct.
*
* @return Boolean php.ini configuration problem?
*/
public function hasPhpIniConfigIssue();
/**
* Returns the PHP configuration file (php.ini) path.
*
* @return string|false php.ini file path
*/
public function getPhpIniConfigPath();
}

View File

@@ -0,0 +1,50 @@
<?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\Setup\System;
interface RequirementInterface
{
/**
* Returns whether the requirement is fulfilled.
*
* @return Boolean true if fulfilled, otherwise false
*/
public function isFulfilled();
/**
* Returns the message for testing the requirement.
*
* @return string The test message
*/
public function getTestMessage();
/**
* Returns the help text for resolving the problem
*
* @return string The help text
*/
public function getHelpText();
/**
* Returns the help text formatted in HTML.
*
* @return string The HTML help
*/
public function getHelpHtml();
/**
* Returns whether this is only an optional recommendation and not a mandatory requirement.
*
* @return Boolean true if optional, false if mandatory
*/
public function isOptional();
}

View File

@@ -23,6 +23,9 @@ class Probe31 implements ProbeInterface
$this->app = $app;
}
/**
* {@inheritdoc}
*/
public function isMigrable()
{
/**
@@ -33,6 +36,9 @@ class Probe31 implements ProbeInterface
&& is_file(__DIR__ . "/../../../../../../config/_GV.php");
}
/**
* {@inheritdoc}
*/
public function getMigration()
{
return new Migration31($this->app);

View File

@@ -23,6 +23,9 @@ class Probe35 implements ProbeInterface
$this->app = $app;
}
/**
* {@inheritdoc}
*/
public function isMigrable()
{
/**
@@ -33,6 +36,9 @@ class Probe35 implements ProbeInterface
&& is_file(__DIR__ . "/../../../../../../config/config.inc");
}
/**
* {@inheritdoc}
*/
public function getMigration()
{
return new Migration35($this->app);

View File

@@ -11,9 +11,17 @@
namespace Alchemy\Phrasea\Setup\Version\Probe;
use Alchemy\Phrasea\Setup\Version\Migration\MigrationInterface;
interface ProbeInterface
{
/**
* @return Boolean
*/
public function isMigrable();
/**
* @return MigrationInterface
*/
public function getMigration();
}

View File

@@ -362,7 +362,7 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
if (!isset(self::$_instance[$id])) {
try {
self::$_instance[$id] = $app['phraseanet.appbox']->get_data_from_cache('_user_' . $id);
self::$_instance[$id]->set_app($app['phraseanet.appbox']);
self::$_instance[$id]->set_app($app);
} catch (Exception $e) {
self::$_instance[$id] = new self($id, $app);
$app['phraseanet.appbox']->set_data_to_cache(self::$_instance[$id], '_user_' . $id);
@@ -1727,7 +1727,7 @@ class User_Adapter implements User_Interface, cache_cacheableInterface
{
$vars = array();
foreach ($this as $key => $value) {
if (in_array($key, array('ACL', 'appbox')))
if (in_array($key, array('ACL', 'app')))
continue;
$vars[] = $key;
}

View File

@@ -1,109 +0,0 @@
<?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.
*/
/**
* @todo write tests
*
* @package KonsoleKomander
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link www.phraseanet.com
*/
use Alchemy\Phrasea\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class module_console_systemConfigCheck extends Command
{
public function __construct($name = null)
{
parent::__construct($name);
$this->setDescription('Check the configuration');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
if ( ! function_exists('_')) {
$output->writeln('<error>YOU MUST ENABLE GETTEXT SUPPORT TO USE PHRASEANET</error>');
$output->writeln('Canceled');
return 1;
}
$ok = true;
if ($this->container['phraseanet.configuration-tester']->isInstalled()) {
$registry = $this->container['phraseanet.registry'];
$output->writeln(_('*** CHECK BINARY CONFIGURATION ***'));
$ok = $this->processConstraints(setup::check_binaries($this->container['phraseanet.registry']), $output) && $ok;
$output->writeln("");
} else {
$registry = new Setup_Registry();
}
$output->writeln(_('*** FILESYSTEM CONFIGURATION ***'));
$ok = $this->processConstraints(setup::check_writability($registry), $output) && $ok;
$output->writeln("");
$output->writeln(_('*** CHECK CACHE OPCODE ***'));
$ok = $this->processConstraints(setup::check_cache_opcode(), $output) && $ok;
$output->writeln("");
$output->writeln(_('*** CHECK CACHE SERVER ***'));
$ok = $this->processConstraints(setup::check_cache_server(), $output) && $ok;
$output->writeln("");
$output->writeln(_('*** CHECK PHP CONFIGURATION ***'));
$ok = $this->processConstraints(setup::check_php_configuration(), $output) && $ok;
$output->writeln("");
$output->writeln(_('*** CHECK PHP EXTENSIONS ***'));
$ok = $this->processConstraints(setup::check_php_extension(), $output) && $ok;
$output->writeln("");
$output->writeln(_('*** CHECK PHRASEA ***'));
$ok = $this->processConstraints(setup::check_phrasea(), $output) && $ok;
$output->writeln("");
$output->writeln(_('*** CHECK SYSTEM LOCALES ***'));
$ok = $this->processConstraints(setup::check_system_locales($this->container), $output) && $ok;
$output->writeln("");
$output->write('Finished !', true);
return (int) ! $ok;
}
protected function processConstraints(Setup_ConstraintsIterator $constraints, OutputInterface $output)
{
$hasError = false;
foreach ($constraints as $constraint) {
if ( ! $this->processConstraint($constraint, $output)) {
$hasError = true;
}
}
return ! $hasError;
}
protected function processConstraint(Setup_Constraint $constraint, OutputInterface $output)
{
$ok = true;
if ($constraint->is_ok()) {
$output->writeln("\t\t<info>" . $constraint->get_message() . '</info>');
} elseif ($constraint->is_blocker()) {
$output->writeln("\t!!!\t<error>" . $constraint->get_message() . '</error>');
$ok = false;
} else {
$output->writeln("\t/!\\\t<comment>" . $constraint->get_message() . '</comment>');
}
return $ok;
}
}

View File

@@ -1,11 +1,44 @@
{% macro board_sub_section(sub_section_title, constraints_type) %}
<h2>{{ sub_section_title }}</h2>
{% macro board_sub_section(requirements) %}
<h2>{{ requirements.getName() }}</h2>
<ul class="setup">
{% for constraint in constraints_type %}
<li class="{% if not constraint.is_ok() %}{% if constraint.is_blocker() %}blocker{% else %}non-blocker{% endif %}{% else %}good-enough{% endif %}">
{{ constraint.get_message() }}
{% if requirements.getRequirements() is not empty %}
<li><strong>{% trans 'Requirements' %}</strong></li>
{% for requirement in requirements.getRequirements() %}
<li class="{% if not requirement.isFulfilled() %}{% if not requirement.isOptional() %}blocker{% else %}non-blocker{% endif %}{% else %}good-enough{% endif %}">
{{ requirement.getTestMessage }}
<p>
{% if not requirement.isFulfilled() %}
{{ requirement.getHelpHtml() | raw }}
{% endif %}
</p>
</li>
{% endfor %}
{% endif %}
{% if requirements.getRecommendations() is not empty %}
<li><strong>{% trans 'Recommendations' %}</strong></li>
{% for requirement in requirements.getRecommendations() %}
<li class="{% if not requirement.isFulfilled() %}{% if not requirement.isOptional() %}blocker{% else %}non-blocker{% endif %}{% else %}good-enough{% endif %}">
{{ requirement.getTestMessage }}
<p>
{% if not requirement.isFulfilled() %}
{{ requirement.getHelpHtml() | raw }}
{% endif %}
</p>
</li>
{% endfor %}
{% endif %}
{% if requirements.getInformations() is not empty %}
<li><strong>{% trans 'Informations' %}</strong></li>
{% for information in requirements.getInformations() %}
<li>
{{ information.getName() }} : {{ information.getValue() }}
</li>
{% endfor %}
{% endif %}
</ul>
{% endmacro %}
@@ -90,7 +123,7 @@
<div class="board_section">
<h1 style="margin-bottom: 0;">{% trans 'setup:: Reglages generaux' %}</h1>
<h2 style="margin-top: 0; font-style: italic;">{% trans 'setup::Votre configuration' %}</h2>
<div class="section_left">
<div class="section">
<h2>{% trans 'setup::Tests d\'envois d\'emails' %}</h2>
<form id="mail_checker" method="post" action="/admin/dashboard/send-mail-test/" target="_self">
@@ -101,96 +134,14 @@
{% endif %}
</form>
{% for constraint in php_version_constraints %}
<h2>{{ constraint.get_name() }}</h2>
<ul class="setup">
<li class="{% if constraint.is_ok() %}good-enough{% else %}blocker{% endif %}">
{{ constraint.get_message() }}
</li>
</ul>
{% endfor %}
{% set sub_section_title %}
{% trans 'setup::Filesystem configuration' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, writability_constraints) }}
{% set sub_section_title %}
{% trans 'setup::Executables' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, binaries_constraints) }}
{% set sub_section_title %}
{% trans 'setup::PHP extensions' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, php_extension_constraints) }}
{% set sub_section_title %}
{% trans 'setup::Serveur de cache' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, cache_constraints) }}
</div>
<div class="section_right">
{% set sub_section_title %}
{% trans 'Phrasea Module' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, phrasea_constraints) }}
{% set sub_section_title %}
{% trans 'setup::Serveur de cache' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, cache_opcode_constraints) }}
<h2>{% trans 'setup:: Serveur Memcached' %}</h2>
<ul class="setup">
{% if app['cache'].isServer() %}
{% set stats = app['cache'].getStats() %}
<li>{% trans 'setup::Serveur actif sur %s' %} {{app['phraseanet.registry'].get('GV_cache_server_host')}} : {{app['phraseanet.registry'].get('GV_cache_server_port')}}</li>
<table>
{% for name, stat in stats%}
<tr class="even"><td>{{ name }}</td><td> {{ stat }}</td></tr>
{% endfor %}
</table>
{% else %}
<li class="non-blocker">{% trans 'setup::Aucun serveur memcached rattache.' %}</li>
{% endif %}
</ul>
{% if app['cache'].isServer() %}
<form id="cache_flusher" method="post" action="/admin/dashboard/flush-cache/">
<input type="submit" id="flush_button" class="btn btn-warning" value="Flush All Caches" />
</form>
{% endif %}
<h2>{% trans 'OPCode cache' %}</h2>
<ul class="setup">
{% if app['opcode-cache'].getName() == 'array' %}
<li class="non-blocker">{% trans 'Array opcode cache is activated, but phrasea strongly recommand the use of APC or Xcache in production' %}</li>
{% else %}
<li>{{ app['opcode-cache'].getName() }}</li>
{% endif %}
</ul>
{% if search_engine_status %}
<h2>{% trans 'setup::Etat du moteur de recherche' %}</h2>
<ul class="setup">
{% for value in search_engine_status %}
<li>{{ value[0] }} : {{ value[1] }}</li>
{% for requirements in app['phraseanet.configuration-tester'].getRequirements() %}
{{ _self.board_sub_section(requirements) }}
{% endfor %}
</ul>
{% else %}
<h2>{% trans 'setup::Sphinx confguration' %}</h2>
<ul class="setup">
<li class="blocker">{% trans 'Search Engine not available' %}</li>
</ul>
{% endif %}
{% set sub_section_title %}
{% trans 'PHP Configuration' %}
{% endset %}
{{ _self.board_sub_section(sub_section_title, php_configuration_constraints) }}
</div>
</div>

View File

@@ -212,43 +212,36 @@ div.switch_right.unchecked {
.board_section #mail_checker {
margin: 0 0 20px 15px;
}
.board_section div[class^="section_"] {
width: 400px;
.board_section div[class="section"] {
height: auto;
}
.board_section div[class^="section_"] h2 {
.board_section div[class="section"] h2 {
line-height: 24px;
}
.board_section div[class^="section_"] ul.setup {
width: 360px;
.board_section div[class="section"] ul.setup {
border: 1px solid #cccccc;
list-style-type: none;
}
.board_section div[class^="section_"] ul.setup li {
.board_section div[class="section"] ul.setup li {
padding: 2px 5px 2px 30px;
background-image: url(/skins/icons/ok.png);
background-repeat: no-repeat;
background-position: 5px center;
}
.board_section div[class^="section_"] ul.setup li.non-blocker {
.board_section div[class="section"] ul.setup li.good-enough {
background-image: url(/skins/icons/ok.png);
}
.board_section div[class="section"] ul.setup li.non-blocker {
background-image: url(/skins/icons/alert.png);
}
.board_section div[class^="section_"] ul.setup li.blocker {
.board_section div[class="section"] ul.setup li.blocker {
background-image: url(/skins/icons/delete.png);
}
.board_section div[class^="section_"] ul.setup li:hover {
.board_section div[class="section"] ul.setup li:hover {
background-color: #fffbcd;
}
.board_section div[class^="section_"] #cache_flusher {
.board_section div[class="section"] #cache_flusher {
margin: -10px 0 20px 0;
}
.board_section .section_left {
float: left;
}
.board_section .section_right {
margin-left: 430px;
padding-top: 1px;
}
.board_section #flush_button {
width: 362px;
margin-left: 15px;

View File

@@ -254,21 +254,21 @@ div.switch_right {
ul, #mail_checker {
margin: 0 0 20px 15px;
}
div[class^="section_"] {
width: 400px;
div[class="section"] {
height: auto;
h2 {
line-height: 24px;
}
ul.setup {
width: 360px;
border: 1px solid @grey;
list-style-type: none;
li {
padding: 2px 5px 2px 30px;
background-image: url(/skins/icons/ok.png);
background-repeat: no-repeat;
background-position: 5px center;
&.good-enough {
background-image: url(/skins/icons/ok.png);
}
&.non-blocker {
background-image: url(/skins/icons/alert.png);
}
@@ -284,13 +284,6 @@ div.switch_right {
margin: -10px 0 20px 0;
}
}
.section_left {
float: left;
}
.section_right {
margin-left: 430px;
padding-top: 1px;
}
#flush_button {
width: 362px;
margin-left: 15px;