Merge branch '3.8'

Conflicts:
	.gitignore
	lib/Alchemy/Phrasea/Core/Version.php
	lib/classes/databox.php
	lib/classes/task/abstract.php
	lib/classes/task/manager.php
	lib/classes/task/period/subdef.php
	lib/classes/task/period/writemeta.php
	lib/conf.d/bases_structure.xml
	templates/web/admin/tasks/list.html.twig
This commit is contained in:
Nicolas Le Goff
2014-08-26 13:38:01 +02:00
16 changed files with 531 additions and 17 deletions

2
.gitignore vendored
View File

@@ -15,4 +15,6 @@ behat.yml
/datas
/www/assets
/tmp-assets
/www/skins/build
/www/crossdomain.xml
/www/scripts/tests/fixtures

View File

@@ -12,6 +12,7 @@
namespace KonsoleKommander;
use Alchemy\Phrasea\Command\Plugin\ListPlugin;
use Alchemy\Phrasea\Command\Setup\CrossDomainGenerator;
use Alchemy\Phrasea\Command\Setup\H264ConfigurationDumper;
use Alchemy\Phrasea\Command\Setup\H264MappingGenerator;
use Alchemy\Phrasea\Command\SearchEngine\IndexFull;
@@ -114,6 +115,7 @@ $cli->command(new H264ConfigurationDumper());
$cli->command(new H264MappingGenerator());
$cli->command(new XSendFileConfigurationDumper());
$cli->command(new XSendFileMappingGenerator());
$cli->command(new CrossDomainGenerator());
if ($cli['phraseanet.SE']->getName() === 'ElasticSearch') {
$cli->command(new IndexFull('searchengine:index'));

View File

@@ -202,3 +202,29 @@ api_cors:
session:
idle: 0
lifetime: 604800 # 1 week
crossdomain:
site-control: 'master-only'
allow-access-from:
-
domain: '*.example.com'
secure: 'false'
-
domain: 'www.example.com'
secure: 'true'
to-ports: '507,516-523'
allow-access-from-identity:
-
fingerprint-algorithm: 'sha-1'
fingerprint: '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67'
-
fingerprint-algorithm: 'sha256'
fingerprint: '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67'
allow-http-request-headers-from:
-
domain: '*.bar.com'
secure: 'true'
headers: 'SOAPAction, X-Foo*'
-
domain: 'foo.example.com'
secure: 'false'
headers: 'Authorization,X-Foo*'

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 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\Utilities\CrossDomainDumper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CrossDomainGenerator extends Command
{
public function __construct($name = null)
{
parent::__construct('crossdomain:generate');
$this->setDescription('Generate crossdomain.xml file according to configuration');
}
/**
* {@inheritdoc}
*/
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$configuration = $this->container['phraseanet.configuration']['crossdomain'];
$dumper = new CrossDomainDumper();
$xml = $dumper->dump($configuration);
$output->writeln($xml);
$this->container['filesystem']->dumpFile($this->container['root.path'].'/www/crossdomain.xml', $xml);
return ;
}
}

View File

@@ -699,7 +699,7 @@ class Databox implements ControllerProviderInterface
$ret['sbas_id'] = $databox_id;
$ret['xml_indexed'] = $datas['xml_indexed'];
$ret['thesaurus_indexed'] = $datas['thesaurus_indexed'];
$ret['jeton_subdef'] = $datas['jeton_subdef'];
if ($app['filesystem']->exists($app['root.path'] . '/config/minilogos/logopdf_' . $databox_id . '.jpg')) {
$ret['printLogoURL'] = '/custom/minilogos/logopdf_' . $databox_id . '.jpg';
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Alchemy\Phrasea\Utilities;
/** Build crossdomain.xml file according to configuration */
class CrossDomainDumper
{
public function dump(array $configuration)
{
$xml = '<?xml version="1.0"?>'.PHP_EOL;
$xml .= '<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">'.PHP_EOL;
$xml .= '<cross-domain-policy>' . PHP_EOL;
$xml .= $this->getSiteControl($configuration);
$xml .= $this->getAllowAccess($configuration);
$xml .= $this->getAllowIdentity($configuration);
$xml .= $this->getAllowHeader($configuration);
$xml .= "</cross-domain-policy>";
return $xml;
}
private function getSiteControl(array $conf)
{
$xml = '';
if (isset($conf['site-control'])) {
$xml = "\t".'<site-control permitted-cross-domain-policies="'.$conf['site-control'].'"/>'.PHP_EOL;
}
return $xml;
}
private function getAllowAccess(array $conf)
{
$xml = '';
if (!isset($conf['allow-access-from'])) {
return $xml;
}
$allowAccess = $conf['allow-access-from'];
if (!is_array($allowAccess)) {
return $xml;
}
foreach ($allowAccess as $access) {
// domain is mandatory
if (!isset($access['domain'])) {
continue;
}
$domain = $access['domain'];
$secure = isset($access['secure']) ? $access['secure'] : false;
$ports = isset($access['to-ports']) ? $access['to-ports'] : false;
$xml .= "\t".'<allow-access-from domain="'.$domain.'"'. ($ports ? ' to-ports="'.$ports.'"' : '') . ($secure ? ' secure="'.$secure.'"': ''). '/>'.PHP_EOL;
}
return $xml;
}
private function getAllowIdentity(array $conf)
{
$xml = '';
if (!isset($conf['allow-access-from-identity'])) {
return $xml;
}
$allowAccess = $conf['allow-access-from-identity'];
if (!is_array($allowAccess)) {
return $xml;
}
foreach ($allowAccess as $access) {
$algorithm = isset($access['fingerprint-algorithm']) ? $access['fingerprint-algorithm'] : false;
$fingerprint = isset($access['fingerprint']) ? $access['fingerprint'] : false;
// both are mandatory
if (!$algorithm || !$fingerprint) {
continue;
}
$xml .= "\t".'<signatory><certificate fingerprint="'.$fingerprint.'" fingerprint-algorithm="'.$algorithm.'"/></signatory>'.PHP_EOL;
}
return $xml;
}
private function getAllowHeader(array $conf)
{
$xml = '';
if (!isset($conf['allow-http-request-headers-from'])) {
return $xml;
}
$allowHeaders = $conf['allow-http-request-headers-from'];
if (!is_array($allowHeaders)) {
return $xml;
}
foreach ($allowHeaders as $access) {
// domain & headers are mandatory
if (!isset($access['domain']) && !isset($access['headers'])) {
continue;
}
$secure = isset($access['secure']) ? $access['secure'] : false;
$xml .= "\t".'<allow-http-request-headers-from domain="'.$access['domain'].'" headers="'.$access['headers'].'"'. ($secure ? ' secure="'.$secure.'"': '') . '/>'.PHP_EOL;
}
return $xml;
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Alchemy\Phrasea\Utilities;
use Alchemy\Phrasea\Exception\RuntimeException;
/** Parse crossdomain.xml file */
class CrossDomainParser
{
public function parse($file)
{
if (!file_exists($file)) {
throw new RuntimeException(sprintf('File "%s" does not exist.'));
}
$xml = simplexml_load_file($file);
if (!$xml) {
throw new RuntimeException(sprintf('File "%s" could not be parsed.'));
}
$conf = array();
if (isset($xml->{"site-control"})) {
$sc = $xml->{"site-control"};
foreach ($sc->attributes() as $k => $v) {
if ($k === 'permitted-cross-domain-policies') {
$conf['site-control'] = (string) $v;
}
}
}
if (isset($xml->{"allow-access-from"})) {
if (count($xml->{"allow-access-from"}) > 0) {
$conf['allow-access-from'] = array();
foreach ($xml->{"allow-access-from"} as $el) {
$opt = array();
foreach ($el->attributes() as $k => $v) {
$opt[$k] = (string) $v;
}
$conf['allow-access-from'][] = $opt;
}
}
}
if (isset($xml->{"signatory"})) {
if (count($xml->{"signatory"}) > 0) {
$conf['allow-access-from-identity'] = array();
foreach ($xml->{"signatory"} as $el) {
if (isset($el->{"certificate"})) {
$c = array();
foreach ($el->{"certificate"}->attributes() as $k => $v) {
$c[$k] = (string) $v;
}
$conf['allow-access-from-identity'][] = $c;
}
}
}
}
if (isset($xml->{"allow-http-request-headers-from"})) {
if (count($xml->{"allow-http-request-headers-from"}) > 0) {
$conf['allow-http-request-headers-from'] = array();
foreach ($xml->{"allow-http-request-headers-from"} as $el) {
$opt = array();
foreach ($el->attributes() as $k => $v) {
$opt[$k] = (string) $v;
}
$conf['allow-http-request-headers-from'][] = $opt;
}
}
}
return $conf;
}
}

View File

@@ -419,14 +419,18 @@ class databox extends base
public function get_indexed_record_amount()
{
$sql = "SELECT status & 3 AS status, SUM(1) AS n FROM record GROUP BY(status & 3)";
$stmt = $this->get_connection()->prepare($sql);
$stmt->execute();
$rs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
$ret = ['xml_indexed' => 0, 'thesaurus_indexed' => 0];
$ret = array(
'xml_indexed' => 0,
'thesaurus_indexed' => 0,
'jeton_subdef' => array()
);
foreach ($rs as $row) {
$status = $row['status'];
if ($status & 1)
@@ -435,6 +439,21 @@ class databox extends base
$ret['thesaurus_indexed'] += $row['n'];
}
$sql = "SELECT type, jeton, COUNT(record_id) AS n FROM record WHERE jeton & ".JETON_MAKE_SUBDEF." GROUP BY type, jeton";
$stmt = $this->get_connection()->prepare($sql);
$stmt->execute();
$rs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
foreach ($rs as $row) {
if(!array_key_exists($row['type'], $ret['jeton_subdef'])) {
$ret['jeton_subdef'][$row['type']] = 0;
}
if((int)$row['jeton'] & JETON_MAKE_SUBDEF) {
$ret['jeton_subdef'][$row['type']] += (int)$row['n'];
}
}
return $ret;
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Utilities\CrossDomainParser;
use Alchemy\Phrasea\Exception\RuntimeException;
class patch_386alpha2a implements patchInterface
{
/** @var string */
private $release = '3.8.6-alpha.2';
/** @var array */
private $concern = array(base::APPLICATION_BOX);
/**
* {@inheritdoc}
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function apply(base $appbox, Application $app)
{
$config = $app['phraseanet.configuration']->getConfig();
$parser = new CrossDomainParser();
try {
$crossDomainConfig = $parser->parse($app['root.path'].'/www/crossdomain.xml');
} catch (RuntimeException $e) {
$crossDomainConfig = array(
'allow-access-from' => array(
array(
'domain' => '*.cooliris.com',
'secure' => 'false',
)
)
);
}
$config['crossdomain'] = $crossDomainConfig;
$app['phraseanet.configuration']->setConfig($config);
return true;
}
}

View File

@@ -2350,14 +2350,22 @@
<default></default>
<comment></comment>
</field>
<field>
<name>completed</name>
<type>tinyint(4)</type>
<null></null>
<extra></extra>
<default>-1</default>
<comment></comment>
</field>
<field>
<name>todo</name>
<type>int(11)</type>
<null></null>
<extra></extra>
<default>0</default>
<comment></comment>
</field>
<field>
<name>done</name>
<type>int(11)</type>
<null></null>
<extra></extra>
<default>0</default>
<comment></comment>
</field>
<field>
<name>runner</name>
<type>char(20)</type>

View File

@@ -199,3 +199,8 @@ session:
idle: 0
# 1 week
lifetime: 604800
crossdomain:
allow-access-from:
-
domain: '*.cooliris.com'
secure: 'false'

View File

@@ -50,6 +50,11 @@
(<a href="{{ path('admin_database_display_document_details', {'databox_id': databox.get_sbas_id()}) }}" class="ajax" target="rights">{{ 'phraseanet:: details' | trans }}</a>)
</li>
<li>
{% trans 'admin::base: subdefs to be created :' %}
<span id="subdefs_todo"></span>
</li>
{% if showDetail %}
<li>
{{ 'admin::base: nombre de mots uniques sur la base :' | trans }}
@@ -276,6 +281,13 @@
p = 100*data.thesaurus_indexed/data.records;
$("#thesaurus_indexed_bar").width(Math.round(2*p));
$("#thesaurus_indexed_percent").text((Math.round(p*100)/100)+" %");
var t = "";
for(var i in data.jeton_subdef)
{
t += (t==""?"":" ; ") + i + ": " + data.jeton_subdef[i];
}
$("#subdefs_todo").text(t);
}
if(data.printLogoURL)

View File

@@ -0,0 +1,87 @@
<?php
namespace Alchemy\Tests\Phrasea\Utilities;
use Alchemy\Phrasea\Utilities\CrossDomainDumper;
class CrossDomainDumperTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider crossDomainProvider
*/
public function testDumper(array $configuration, $expected)
{
$dumper = new CrossDomainDumper();
$this->assertEquals($dumper->dump($configuration), $expected);
}
public function crossDomainProvider()
{
return array(
array(
array(
'site-control' => 'master-only',
'allow-access-from' => array(
array(
'domain'=> '*.example.com',
'secure'=> 'false'
),
array(
'domain'=> 'www.example.com',
'secure'=>'true',
'to-ports'=>'507,516-523'
)
),
'allow-access-from-identity' => array(
array(
'fingerprint-algorithm'=> 'sha-1',
'fingerprint'=> '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67'
),
array(
'fingerprint-algorithm'=> 'sha256',
'fingerprint' => '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67'
)
),
'allow-http-request-headers-from' => array(
array(
'domain'=> '*.bar.com',
'secure'=> 'true',
'headers'=> 'SOAPAction, X-Foo*'
),
array(
'domain'=> 'foo.example.com',
'secure'=> 'false',
'headers'=> 'Authorization,X-Foo*'
)
),
),
'<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only"/>
<allow-access-from domain="*.example.com" secure="false"/>
<allow-access-from domain="www.example.com" to-ports="507,516-523" secure="true"/>
<signatory><certificate fingerprint="01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67" fingerprint-algorithm="sha-1"/></signatory>
<signatory><certificate fingerprint="01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67" fingerprint-algorithm="sha256"/></signatory>
<allow-http-request-headers-from domain="*.bar.com" headers="SOAPAction, X-Foo*" secure="true"/>
<allow-http-request-headers-from domain="foo.example.com" headers="Authorization,X-Foo*" secure="false"/>
</cross-domain-policy>'
),
array(
array(
'allow-access-from' => array(
array(
'domain'=> '*.cooliris.com',
'secure'=> 'false'
)
)
),
'<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*.cooliris.com" secure="false"/>
</cross-domain-policy>'
)
);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Alchemy\Tests\Phrasea\Utilities;
use Alchemy\Phrasea\Utilities\CrossDomainParser;
class CrossDomainParserTest extends \PHPUnit_Framework_TestCase
{
public function testParser()
{
$parser = new CrossDomainParser();
$this->assertEquals($parser->parse(__DIR__.'/fixture.crossdomain.xml'), array(
'site-control' => 'master-only',
'allow-access-from' => array(
array(
'domain'=> '*.example.com',
'secure'=> 'false'
),
array(
'domain'=> 'www.example.com',
'secure'=>'true',
'to-ports'=>'507,516-523'
)
),
'allow-access-from-identity' => array(
array(
'fingerprint-algorithm'=> 'sha-1',
'fingerprint'=> '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67'
),
array(
'fingerprint-algorithm'=> 'sha256',
'fingerprint' => '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67'
)
),
'allow-http-request-headers-from' => array(
array(
'domain'=> '*.bar.com',
'secure'=> 'true',
'headers'=> 'SOAPAction, X-Foo*'
),
array(
'domain'=> 'foo.example.com',
'secure'=> 'false',
'headers'=> 'Authorization,X-Foo*'
)
)
));
}
}

View File

@@ -0,0 +1,10 @@
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only"/>
<allow-access-from domain="*.example.com" secure="false"/>
<allow-access-from domain="www.example.com" to-ports="507,516-523" secure="true"/>
<signatory><certificate fingerprint="01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67" fingerprint-algorithm="sha-1"/></signatory>
<signatory><certificate fingerprint="01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67" fingerprint-algorithm="sha256"/></signatory>
<allow-http-request-headers-from domain="*.bar.com" headers="SOAPAction, X-Foo*" secure="true"/>
<allow-http-request-headers-from domain="foo.example.com" headers="Authorization,X-Foo*" secure="false"/>
</cross-domain-policy>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM
"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*.cooliris.com" secure="false" />
</cross-domain-policy>