Merge branch 'master' into PHRAS-1798_update_version_name

This commit is contained in:
Nicolas Maillat
2018-06-26 13:13:19 +00:00
committed by GitHub
675 changed files with 45296 additions and 7600 deletions

View File

@@ -1,3 +0,0 @@
{
"directory": "www/bower_components"
}

View File

@@ -5,14 +5,11 @@ branches:
except: except:
- /^3\..*$/ - /^3\..*$/
- /^scrutinizer-.*$/ - /^scrutinizer-.*$/
- master
- 4.0
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures:
- php: 7.0
env: TEST_SUITE=1
- php: 7.0
env: TEST_SUITE=2
include: include:
- php: 5.5 - php: 5.5
env: TEST_SUITE=1 env: TEST_SUITE=1
@@ -41,22 +38,31 @@ before_install:
- curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.6.0.deb && sudo dpkg -i --force-confnew elasticsearch-1.6.0.deb - curl -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.6.0.deb && sudo dpkg -i --force-confnew elasticsearch-1.6.0.deb
- sudo /usr/share/elasticsearch/bin/plugin install elasticsearch/elasticsearch-analysis-icu/2.6.0 - sudo /usr/share/elasticsearch/bin/plugin install elasticsearch/elasticsearch-analysis-icu/2.6.0
- sudo service elasticsearch start - sudo service elasticsearch start
- sudo apt-get install libzmq-dev
- yes '' | pecl install zmq-beta
- echo 'session.cache_limiter = ""' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - echo 'session.cache_limiter = ""' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- echo 'extension="redis.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - echo 'extension="redis.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- if [[ $TRAVIS_PHP_VERSION = 5.* ]];then echo 'extension="memcache.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; - if [[ $TRAVIS_PHP_VERSION = 5.* ]];then echo 'extension="memcache.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi;
- echo 'extension="memcached.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - echo 'extension="memcached.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- echo "extension=zmq.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- yes | pecl install imagick-beta - yes | pecl install imagick-beta
- sudo apt-get install librabbitmq-dev # ok php 5
- pecl install amqp-1.4.0 #- sudo apt-get install librabbitmq-dev
#- pecl install amqp-1.4.0
# ok php 5 and php 7
- git clone https://github.com/alanxz/rabbitmq-c
- cd rabbitmq-c
- git checkout 2ca1774489328cde71195f5fa95e17cf3a80cb8a
- git submodule init && git submodule update && autoreconf -i && ./configure && make && sudo make install
- cd ..
- yes '' | pecl install amqp-1.9.3
install: install:
- if [[ $TRAVIS_PHP_VERSION = 5.* ]];then travis_retry composer install --no-progress --no-interaction --optimize-autoloader; fi; - travis_retry composer install --no-progress --no-interaction --optimize-autoloader
- if [[ $TRAVIS_PHP_VERSION = 7.* ]];then travis_retry composer update --no-progress --no-interaction --optimize-autoloader; fi;
- travis_retry npm install - travis_retry npm install
before_script: before_script:
- mysql -e 'CREATE DATABASE update39_test;CREATE DATABASE ab_test;CREATE DATABASE db_test;SET @@global.sql_mode=STRICT_ALL_TABLES;SET @@global.max_allowed_packet=33554432;SET @@global.wait_timeout=999999;' - mysql -e 'CREATE DATABASE update39_test;CREATE DATABASE ab_test;CREATE DATABASE db_test;SET @@global.sql_mode=STRICT_ALL_TABLES;SET @@global.max_allowed_packet=33554432;SET @@global.wait_timeout=999999;'
- "./bin/developer system:uninstall" - "./bin/developer system:uninstall"
- "./bin/setup system:install --email=test@phraseanet.com --password=test --db-host=127.0.0.1 --db-user=root --db-template=fr --db-password= --databox=db_test --appbox=ab_test --server-name=http://127.0.0.1 -y;" - "./bin/setup system:install --email=test@phraseanet.com --password=test --db-host=127.0.0.1 --db-user=root --db-template=fr-simple --db-password= --databox=db_test --appbox=ab_test --server-name=http://127.0.0.1 -y;"
- | - |
./bin/developer ini:reset --email=test@phraseanet.com --password=test --run-patches --no-setup-dbs; ./bin/developer ini:reset --email=test@phraseanet.com --password=test --run-patches --no-setup-dbs;
php resources/hudson/cleanupSubdefs.php; php resources/hudson/cleanupSubdefs.php;

View File

@@ -8,7 +8,7 @@ install_composer:
composer install composer install
install_asset_dependencies: install_asset_dependencies:
npm install yarn
./node_modules/.bin/gulp build ./node_modules/.bin/gulp build
install_assets: install_assets:
@@ -17,7 +17,6 @@ install_assets:
clean_assets: clean_assets:
rm -rf ./node_modules rm -rf ./node_modules
rm -rf ./www/assets rm -rf ./www/assets
rm -rf ./www/bower_components
mkdir ./node_modules mkdir ./node_modules
touch ./node_modules/.gitkeep touch ./node_modules/.gitkeep

11
Vagrantfile vendored
View File

@@ -1,8 +1,15 @@
Vagrant.require_version ">= 1.5" Vagrant.require_version ">= 1.5"
$php = [ "5.6", "7.0", "7.1", "7.2" ]
$phpVersion = ENV['phpversion'] ? ENV['phpversion'] : "5.6";
unless Vagrant.has_plugin?('vagrant-hostmanager') unless Vagrant.has_plugin?('vagrant-hostmanager')
raise "vagrant-hostmanager is not installed! Please run\n vagrant plugin install vagrant-hostmanager\n\n" raise "vagrant-hostmanager is not installed! Please run\n vagrant plugin install vagrant-hostmanager\n\n"
end end
unless $php.include?($phpVersion)
raise "You should specify php version before running vagrant\n\n (Available : 5.6, 7.0, 7.1, 7.2 | default => 5.6)\n\n Exemple: phpversion='7.0' vagrant up \n\n"
end
$root = File.dirname(File.expand_path(__FILE__)) $root = File.dirname(File.expand_path(__FILE__))
# Check to determine whether we're on a windows or linux/os-x host, # Check to determine whether we're on a windows or linux/os-x host,
@@ -54,7 +61,6 @@ else
end end
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
# Configure hostmanager # Configure hostmanager
config.hostmanager.enabled = true config.hostmanager.enabled = true
config.hostmanager.manage_host = true config.hostmanager.manage_host = true
@@ -88,6 +94,7 @@ Vagrant.configure("2") do |config|
ansible.extra_vars = { ansible.extra_vars = {
hostname: $hostname, hostname: $hostname,
host_addresses: $hostIps, host_addresses: $hostIps,
phpversion: $phpVersion,
postfix: { postfix: {
postfix_domain: $hostname + ".vb" postfix_domain: $hostname + ".vb"
} }
@@ -104,7 +111,7 @@ Vagrant.configure("2") do |config|
} }
end end
else else
config.vm.provision :shell, path: "resources/ansible/windows.sh", args: ["default"] config.vm.provision :shell, path: "resources/ansible/windows.sh", args: ["default", $phpVersion]
# config.vm.provision :shell, run: "always", path: "resources/ansible/windows-always.sh", args: ["default"] # config.vm.provision :shell, run: "always", path: "resources/ansible/windows-always.sh", args: ["default"]
end end

View File

@@ -18,6 +18,7 @@ use Alchemy\Phrasea\Command\SearchEngine\Debug\QueryParseCommand;
use Alchemy\Phrasea\Command\SearchEngine\Debug\QuerySampleCommand; use Alchemy\Phrasea\Command\SearchEngine\Debug\QuerySampleCommand;
use Alchemy\Phrasea\Command\SearchEngine\IndexCreateCommand; use Alchemy\Phrasea\Command\SearchEngine\IndexCreateCommand;
use Alchemy\Phrasea\Command\SearchEngine\IndexDropCommand; use Alchemy\Phrasea\Command\SearchEngine\IndexDropCommand;
use Alchemy\Phrasea\Command\SearchEngine\IndexManipulateCommand;
use Alchemy\Phrasea\Command\SearchEngine\IndexPopulateCommand; use Alchemy\Phrasea\Command\SearchEngine\IndexPopulateCommand;
use Alchemy\Phrasea\Command\Thesaurus\FindConceptsCommand; use Alchemy\Phrasea\Command\Thesaurus\FindConceptsCommand;
use Alchemy\Phrasea\Core\Version; use Alchemy\Phrasea\Core\Version;
@@ -123,6 +124,7 @@ $cli->command(new H264MappingGenerator());
$cli->command(new XSendFileConfigurationDumper()); $cli->command(new XSendFileConfigurationDumper());
$cli->command(new XSendFileMappingGenerator()); $cli->command(new XSendFileMappingGenerator());
$cli->command(new IndexManipulateCommand());
$cli->command(new IndexCreateCommand()); $cli->command(new IndexCreateCommand());
$cli->command(new IndexDropCommand()); $cli->command(new IndexDropCommand());
$cli->command(new MappingUpdateCommand()); $cli->command(new MappingUpdateCommand());

View File

@@ -12,6 +12,7 @@
namespace KonsoleKommander; namespace KonsoleKommander;
use Alchemy\Phrasea\Command\Setup\ConfigurationEditor; use Alchemy\Phrasea\Command\Setup\ConfigurationEditor;
use Alchemy\Phrasea\Command\Setup\FixAutoincrements;
use Alchemy\Phrasea\Core\Version; use Alchemy\Phrasea\Core\Version;
use Alchemy\Phrasea\Command\UpgradeDBDatas; use Alchemy\Phrasea\Command\UpgradeDBDatas;
use Alchemy\Phrasea\Command\Setup\Install; use Alchemy\Phrasea\Command\Setup\Install;
@@ -74,8 +75,9 @@ $app->command(new PluginsReset());
$app->command(new EnablePlugin()); $app->command(new EnablePlugin());
$app->command(new DisablePlugin()); $app->command(new DisablePlugin());
$app->command(new CheckEnvironment('check:system')); $app->command(new CheckEnvironment('check:system'));
$app->command(new Install('system:install')); $app->command(new Install('system:install', $app['phraseanet.structure-template']));
$app->command(new CrossDomainGenerator()); $app->command(new CrossDomainGenerator());
$app->command(new FixAutoincrements('system:fix-autoincrements'));
$app['phraseanet.setup_mode'] = true; $app['phraseanet.setup_mode'] = true;

View File

@@ -1,46 +0,0 @@
{
"name": "Phraseanet",
"version": "4.0.0",
"dependencies": {
"jquery": "~1.11.3",
"jquery-ui": "~1.10.4",
"jquery-mobile-bower": "~1.3.0",
"underscore": "~1.4.4",
"font-awesome": "~3.2.1",
"modernizr": "~2.8.3",
"normalize-css": "~2.1.3",
"json2": "latest",
"humane-js": "~3.0.6",
"jquery-file-upload": "~8.3.2",
"blueimp-load-image": "latest",
"requirejs": "~2.1",
"backbone-amd": "~1.0",
"underscore-amd": "~1.4",
"i18next": "~1.6",
"bootstrap-multiselect": "v0.9",
"zxcvbn": "https://github.com/lowe/zxcvbn.git",
"geonames-server-jquery-plugin": "~0.2",
"tinymce": "~4.0",
"jquery-galleria": "1.2.9",
"jquery.cookie": "~1.4",
"fancytree": "~2.7",
"bootstrap-sass": "v2.3.2.2",
"jquery.lazyload": "~1.9.7",
"jquery-treeview": "~1.4.2",
"alchemy-embed-medias": "~0.3.4",
"html5shiv": "^3.7.3",
"jquery-simplecolorpicker": "^0.3.1"
},
"devDependencies": {
"mocha": "latest",
"sinonjs": "~1.7.0",
"chai": "~1.6",
"squire": "~0",
"sinon-chai": "~2.5",
"qunit": "https://github.com/jquery/qunit.git#1.11.0",
"js-fixtures": "https://github.com/badunk/js-fixtures/archive/master.zip"
},
"resolutions": {
"jquery": "~1.11.3"
}
}

View File

@@ -7,7 +7,7 @@ general:
machine: machine:
php: php:
version: 5.5.31 version: 7.0.24 #5.6.22
node: node:
version: stable version: stable
services: services:
@@ -18,16 +18,16 @@ machine:
dependencies: dependencies:
cache_directories: cache_directories:
- elasticsearch-1.6.0 # relative to the build directory - elasticsearch-2.3.3 # relative to the build directory
- node_modules - node_modules
- ~/.composer - ~/.composer
pre: pre:
- git clone https://github.com/alanxz/rabbitmq-c - git clone https://github.com/alanxz/rabbitmq-c
- cd rabbitmq-c && git checkout 2ca1774489328cde71195f5fa95e17cf3a80cb8a - cd rabbitmq-c && git checkout 2ca1774489328cde71195f5fa95e17cf3a80cb8a
- cd rabbitmq-c && git submodule init && git submodule update && autoreconf -i && ./configure && make && sudo make install - cd rabbitmq-c && git submodule init && git submodule update && autoreconf -i && ./configure && make && sudo make install
- pecl install amqp-1.6.0 - pecl channel-update pear.php.net
- yes '' | pecl install amqp-1.9.3
- yes '' | pecl install imagick - yes '' | pecl install imagick
- pecl install json
- sudo apt-get install libzmq-dev - sudo apt-get install libzmq-dev
- yes '' | pecl install zmq-beta - yes '' | pecl install zmq-beta
- echo "extension = amqp.so" > /opt/circleci/php/$(phpenv global)/etc/conf.d/amqp.ini - echo "extension = amqp.so" > /opt/circleci/php/$(phpenv global)/etc/conf.d/amqp.ini
@@ -38,21 +38,28 @@ dependencies:
override: override:
- composer install --no-progress --no-interaction --optimize-autoloader - composer install --no-progress --no-interaction --optimize-autoloader
post: post:
- node -v
- npm -v
- npm install - npm install
- if [[ ! -e elasticsearch-1.6.0 ]]; then wget --no-check-certificate https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.6.0.tar.gz && tar -xvf elasticsearch-1.6.0.tar.gz && elasticsearch-1.6.0/bin/plugin install elasticsearch/elasticsearch-analysis-icu/2.6.0; fi - if [[ ! -e elasticsearch-2.3.3 ]]; then wget --no-check-certificate https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-2.3.3.tar.gz && tar -xvf elasticsearch-2.3.3.tar.gz && elasticsearch-2.3.3/bin/plugin install analysis-icu; fi
- elasticsearch-1.6.0/bin/elasticsearch: {background: true} - elasticsearch-2.3.3/bin/elasticsearch: {background: true}
database: database:
override: override:
- mysql -u ubuntu -e 'CREATE DATABASE update39_test;CREATE DATABASE ab_test;CREATE DATABASE db_test;SET @@global.sql_mode=STRICT_ALL_TABLES;SET @@global.max_allowed_packet=33554432;SET @@global.wait_timeout=999999;'; - mysql -u ubuntu -e 'CREATE DATABASE update39_test;CREATE DATABASE ab_test;CREATE DATABASE db_test;SET @@global.sql_mode=STRICT_ALL_TABLES;SET @@global.max_allowed_packet=33554432;SET @@global.wait_timeout=999999;';
post: post:
- "./bin/developer system:uninstall -v" - "./bin/developer system:uninstall -v"
- "./bin/setup system:install -v --email=test@phraseanet.com --password=test --db-host=127.0.0.1 --db-user=ubuntu --db-template=fr --db-password= --databox=db_test --appbox=ab_test --server-name=http://127.0.0.1 -y;" - "./bin/setup system:install -v --email=test@phraseanet.com --password=test --db-host=127.0.0.1 --db-user=ubuntu --db-template=fr-simple --db-password= --databox=db_test --appbox=ab_test --server-name=http://127.0.0.1 -y;"
- "./bin/developer ini:setup-tests-dbs -v" - "./bin/developer ini:setup-tests-dbs -v"
- "./bin/console searchengine:index:create -v" - "./bin/console searchengine:index:create -v"
- "./bin/developer phraseanet:regenerate-sqlite -v" - "./bin/developer phraseanet:regenerate-sqlite -v"
- "./bin/developer phraseanet:generate-js-fixtures -v" - "./bin/developer phraseanet:generate-js-fixtures -v"
#test:
# override:
# - case $CIRCLE_NODE_INDEX in 0) EXIT=0; php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-unit.xml --exclude-group legacy || EXIT=$?; php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-legacy-no-web.xml --group legacy --exclude-group web || EXIT=$?; exit $EXIT;; 1) php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-legacy-web.xml --group web ;; esac:
# #- php -d memory_limit=-1 bin/phpunit -c phpunit.xml.dist;
test: test:
override: override:
- case $CIRCLE_NODE_INDEX in 0) EXIT=0; php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-unit.xml --exclude-group legacy || EXIT=$?; php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-legacy-no-web.xml --group legacy --exclude-group web || EXIT=$?; exit $EXIT;; 1) php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-legacy-web.xml --group web ;; esac: - case $CIRCLE_NODE_INDEX in 0) EXIT=0; php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-unit.xml --exclude-group legacy || EXIT=$?; php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-legacy-no-web.xml --group legacy --exclude-group web || EXIT=$?; exit $EXIT;; 1) php -d memory_limit=-1 bin/phpunit --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit-legacy-web.xml --group web ;; esac:

View File

@@ -67,7 +67,6 @@
"doctrine/migrations": "^1.0.0", "doctrine/migrations": "^1.0.0",
"doctrine/orm": "^2.4.0", "doctrine/orm": "^2.4.0",
"elasticsearch/elasticsearch": "~2.0", "elasticsearch/elasticsearch": "~2.0",
"facebook/php-sdk": "~3.2.3",
"firebase/php-jwt": "^3.0.0", "firebase/php-jwt": "^3.0.0",
"gedmo/doctrine-extensions": "~2.3.0", "gedmo/doctrine-extensions": "~2.3.0",
"goodby/csv": "^1.3.0", "goodby/csv": "^1.3.0",
@@ -119,7 +118,8 @@
"zend/gdata": "~1.12.1", "zend/gdata": "~1.12.1",
"alchemy/worker-bundle": "^0.1.6", "alchemy/worker-bundle": "^0.1.6",
"alchemy/queue-bundle": "^0.1.5", "alchemy/queue-bundle": "^0.1.5",
"google/recaptcha": "^1.1" "google/recaptcha": "^1.1",
"facebook/graph-sdk": "^5.6"
}, },
"require-dev": { "require-dev": {
"mikey179/vfsStream": "~1.5", "mikey179/vfsStream": "~1.5",

366
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -102,6 +102,7 @@ border-manager:
enabled: false enabled: false
options: options:
colorspaces: [cmyk, grayscale, rgb] colorspaces: [cmyk, grayscale, rgb]
media_types: [Image]
- -
type: Checker\Dimension type: Checker\Dimension
enabled: false enabled: false
@@ -135,6 +136,7 @@ authentication:
options: options:
app-id: '' app-id: ''
secret: '' secret: ''
default-graph-version: 'v2.10'
twitter: twitter:
enabled: false enabled: false
options: options:
@@ -237,9 +239,40 @@ embed_bundle:
player: flexpaper player: flexpaper
enable-pdfjs: true enable-pdfjs: true
geocoding-providers: geocoding-providers:
- -
name: 'mapBox' map-provider: mapboxWebGL
public-key: '' enabled: false
public-key: ''
map-layers:
-
name: Light
value: 'mapbox://styles/mapbox/light-v9'
-
name: Streets
value: 'mapbox://styles/mapbox/streets-v9'
-
name: Basic
value: 'mapbox://styles/mapbox/basic-v9'
-
name: Satellite
value: 'mapbox://styles/mapbox/satellite-v9'
-
name: Dark
value: 'mapbox://styles/mapbox/dark-v9'
transition-mapboxgl:
-
animate: true
speed: '2.2'
curve: '1.42'
default-position:
- '48.879162'
- '2.335062'
default-zoom: 5
marker-default-zoom: 9
geonames-field-mapping: true
cityfields: City, Ville
provincefields: Province
countryfields: Country, Pays
workers: workers:
queue: queue:
worker-queue: worker-queue:

View File

@@ -28,6 +28,9 @@
// Rest // Rest
%token database database %token database database
%token collection collection %token collection collection
%token sha256 sha256
%token geolocation geolocation
%token uuid uuid
%token type type %token type type
%token id id|recordid %token id id|recordid
%token created_on created_(on|at) %token created_on created_(on|at)
@@ -86,12 +89,15 @@ match_key:
#native_key: #native_key:
<database> <database>
| <sha256>
| <uuid>
| <collection> | <collection>
| <type> | <type>
| <id> | <id>
key: key:
timestamp_key() geolocation_key()
| timestamp_key()
| ::meta_prefix:: meta_key() | ::meta_prefix:: meta_key()
| ::field_prefix:: field_key() | ::field_prefix:: field_key()
| field_key() | field_key()
@@ -100,6 +106,9 @@ key:
<created_on> <created_on>
| <updated_on> | <updated_on>
#geolocation_key:
<geolocation>
#meta_key: #meta_key:
word_or_keyword()+ word_or_keyword()+
@@ -169,6 +178,9 @@ keyword:
| <or> | <or>
| <database> | <database>
| <collection> | <collection>
| <sha256>
| <geolocation>
| <uuid>
| <type> | <type>
| <id> | <id>
| <created_on> | <created_on>

View File

@@ -55,6 +55,7 @@ class RouteLoader
'/prod/records/property' => Providers\Prod\Property::class, '/prod/records/property' => Providers\Prod\Property::class,
'/prod/share/' => Providers\Prod\Share::class, '/prod/share/' => Providers\Prod\Share::class,
'/prod/story' => Providers\Prod\Story::class, '/prod/story' => Providers\Prod\Story::class,
'/prod/subdefs' => Providers\Prod\Subdefs::class,
'/prod/tools/' => Providers\Prod\Tools::class, '/prod/tools/' => Providers\Prod\Tools::class,
'/prod/tooltip' => Providers\Prod\Tooltip::class, '/prod/tooltip' => Providers\Prod\Tooltip::class,
'/prod/TOU/' => Providers\Prod\TOU::class, '/prod/TOU/' => Providers\Prod\TOU::class,

View File

@@ -21,13 +21,14 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
class Facebook extends AbstractProvider class Facebook extends AbstractProvider
{ {
/** @var \Facebook */ /** @var \Facebook\Facebook */
private $facebook; private $facebook;
public function __construct(\Facebook $facebook, UrlGenerator $generator) public function __construct(\Facebook\Facebook $facebook, UrlGenerator $generator, SessionInterface $session)
{ {
$this->facebook = $facebook; $this->facebook = $facebook;
$this->generator = $generator; $this->generator = $generator;
$this->session = $session;
} }
/** /**
@@ -49,16 +50,20 @@ class Facebook extends AbstractProvider
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate(array $params = array())
{ {
return new RedirectResponse($this->facebook->getLoginUrl([ $params = array_merge(['providerId' => $this->getId()], $params);
'scope' => 'email',
'redirect_uri' => $this->generator->generate( return new RedirectResponse(
'login_authentication_provider_callback', $this->facebook->getRedirectLoginHelper()->getLoginUrl(
['providerId' => $this->getId()], $this->generator->generate(
UrlGenerator::ABSOLUTE_URL 'login_authentication_provider_callback',
$params,
UrlGenerator::ABSOLUTE_URL
),
['email']
) )
])); );
} }
/** /**
@@ -66,15 +71,15 @@ class Facebook extends AbstractProvider
*/ */
public function logout() public function logout()
{ {
$this->facebook->destroySession(); $this->session->remove('fb_access_token');
} }
/** /**
* @param \Facebook $facebook * @param \Facebook\Facebook $facebook
* *
* @return Facebook * @return Facebook
*/ */
public function setFacebook(\Facebook $facebook) public function setFacebook(\Facebook\Facebook $facebook)
{ {
$this->facebook = $facebook; $this->facebook = $facebook;
@@ -82,35 +87,55 @@ class Facebook extends AbstractProvider
} }
/** /**
* @return \Facebook * @return \Facebook\Facebook
*/ */
public function getFacebook() public function getFacebook()
{ {
return $this->facebook; return $this->facebook;
} }
/**
* @param $dataToRetrieve
* @return \Facebook\GraphNodes\GraphUser
*/
protected function getGraphUser($dataToRetrieve)
{
try {
$response = $this->facebook->get(
"/me?fields=" . implode(',', $dataToRetrieve),
$this->session->get('fb_access_token')
);
} catch(\Facebook\Exceptions\FacebookResponseException $e) {
throw new NotAuthenticatedException('Graph returned an error: ' . $e->getMessage());
exit;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
throw new NotAuthenticatedException('Facebook SDK returned an error: ' . $e->getMessage());
exit;
}
if (!$response)
{
throw new NotAuthenticatedException('Not authenticated');
}
return $response->getGraphUser();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getIdentity() public function getIdentity()
{ {
try { $user = $this->getGraphUser(['id', 'name', 'email', 'picture', 'last_name', 'first_name']);
$data = $this->facebook->api('/me');
$identity = new Identity(); $identity = new Identity();
$identity->set(Identity::PROPERTY_ID, $data['id']); $identity->set(Identity::PROPERTY_ID, $user['id']);
$identity->set(Identity::PROPERTY_IMAGEURL, sprintf( $identity->set(Identity::PROPERTY_IMAGEURL, $user['picture']);
'https://graph.facebook.com/%s/picture?return_ssl_resources=1', $identity->set(Identity::PROPERTY_EMAIL, $user['email']);
$data['username'] $identity->set(Identity::PROPERTY_FIRSTNAME, $user['first_name']);
)); $identity->set(Identity::PROPERTY_LASTNAME, $user['last_name']);
$identity->set(Identity::PROPERTY_EMAIL, $data['email']); $identity->set(Identity::PROPERTY_USERNAME, $user['first_name']);
$identity->set(Identity::PROPERTY_FIRSTNAME, $data['first_name']);
$identity->set(Identity::PROPERTY_LASTNAME, $data['last_name']);
$identity->set(Identity::PROPERTY_USERNAME, $data['username']);
} catch (\FacebookApiException $e) {
throw new NotAuthenticatedException('Unable to get profile informations', $e->getCode(), $e);
}
return $identity; return $identity;
} }
@@ -120,9 +145,44 @@ class Facebook extends AbstractProvider
*/ */
public function onCallback(Request $request) public function onCallback(Request $request)
{ {
if (!$this->facebook->getUser()) { $helper = $this->facebook->getRedirectLoginHelper();
throw new NotAuthenticatedException('Facebook authentication failed');
try {
$accessToken = $helper->getAccessToken();
} catch(\Facebook\Exceptions\FacebookResponseException $e) {
throw new NotAuthenticatedException('Graph returned an error: ' . $e->getMessage());
exit;
} catch(\Facebook\Exceptions\FacebookSDKException $e) {
throw new NotAuthenticatedException('Facebook SDK returned an error: ' . $e->getMessage());
exit;
} }
if (! isset($accessToken)) {
if ($helper->getError()) {
$error = "Error: " . $helper->getError() . "\n";
$error .= "Error Code: " . $helper->getErrorCode() . "\n";
$error .= "Error Reason: " . $helper->getErrorReason() . "\n";
$error .= "Error Description: " . $helper->getErrorDescription() . "\n";
throw new NotAuthenticatedException($error);
} else {
throw new NotAuthenticatedException('Facebook authentication failed');
}
exit;
}
$oAuth2Client = $this->facebook->getOAuth2Client();
if (! $accessToken->isLongLived()) {
try {
$accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
} catch (\Facebook\Exceptions\FacebookSDKException $e) {
throw new NotAuthenticatedException('Error getting long-lived access token: ' . $e->getMessage() );
exit;
}
}
$this->session->set('fb_access_token', (string) $accessToken);
} }
/** /**
@@ -130,11 +190,9 @@ class Facebook extends AbstractProvider
*/ */
public function getToken() public function getToken()
{ {
if (0 >= $this->facebook->getUser()) { $user = $this->getGraphUser(['id']);
throw new NotAuthenticatedException('Provider has not authenticated');
}
return new Token($this, $this->facebook->getUser()); return new Token($this, $user['id']);
} }
/** /**
@@ -194,11 +252,12 @@ class Facebook extends AbstractProvider
*/ */
public static function create(UrlGenerator $generator, SessionInterface $session, array $options) public static function create(UrlGenerator $generator, SessionInterface $session, array $options)
{ {
$config['appId'] = $options['app-id']; $config['app_id'] = $options['app-id'];
$config['secret'] = $options['secret']; $config['app_secret'] = $options['secret'];
$config['default_graph_version'] = $options['default-graph-version'];
$facebook = new \Facebook($config); $facebook = new \Facebook\Facebook($config);
return new static($facebook, $generator); return new static($facebook, $generator, $session);
} }
} }

View File

@@ -78,8 +78,10 @@ class Github extends AbstractProvider
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate(array $params = array())
{ {
$params = array_merge(['providerId' => $this->getId()], $params);
$state = $this->createState(); $state = $this->createState();
$this->session->set('github.provider.state', $state); $this->session->set('github.provider.state', $state);
@@ -90,7 +92,7 @@ class Github extends AbstractProvider
'state' => $state, 'state' => $state,
'redirect_uri' => $this->generator->generate( 'redirect_uri' => $this->generator->generate(
'login_authentication_provider_callback', 'login_authentication_provider_callback',
['providerId' => $this->getId()], $params,
UrlGenerator::ABSOLUTE_URL UrlGenerator::ABSOLUTE_URL
), ),
], '', '&')); ], '', '&'));

View File

@@ -41,14 +41,6 @@ class GooglePlus extends AbstractProvider
'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.profile',
]); ]);
$this->client->setRedirectUri(
$this->generator->generate(
'login_authentication_provider_callback', [
'providerId' => $this->getId(),
], UrlGenerator::ABSOLUTE_URL
)
);
$this->client->setApprovalPrompt("auto"); $this->client->setApprovalPrompt("auto");
if ($this->session->has('google-plus.provider.token')) { if ($this->session->has('google-plus.provider.token')) {
@@ -115,8 +107,18 @@ class GooglePlus extends AbstractProvider
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate(array $params = array())
{ {
$params = array_merge(['providerId' => $this->getId()], $params);
$this->client->setRedirectUri(
$this->generator->generate(
'login_authentication_provider_callback',
$params,
UrlGenerator::ABSOLUTE_URL
)
);
$state = $this->createState(); $state = $this->createState();
$this->session->set('google-plus.provider.state', $state); $this->session->set('google-plus.provider.state', $state);

View File

@@ -78,8 +78,10 @@ class Linkedin extends AbstractProvider
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate(array $params = array())
{ {
$params = array_merge(['providerId' => $this->getId()], $params);
$state = $this->createState(); $state = $this->createState();
$this->session->set('linkedin.provider.state', $state); $this->session->set('linkedin.provider.state', $state);
@@ -91,7 +93,7 @@ class Linkedin extends AbstractProvider
'state' => $state, 'state' => $state,
'redirect_uri' => $this->generator->generate( 'redirect_uri' => $this->generator->generate(
'login_authentication_provider_callback', 'login_authentication_provider_callback',
['providerId' => $this->getId()], $params,
UrlGenerator::ABSOLUTE_URL UrlGenerator::ABSOLUTE_URL
), ),
], '', '&')); ], '', '&'));

View File

@@ -43,9 +43,11 @@ interface ProviderInterface
/** /**
* Redirects to the actual authentication provider * Redirects to the actual authentication provider
* *
* @param array $params
*
* @return RedirectResponse * @return RedirectResponse
*/ */
public function authenticate(); public function authenticate(array $params);
/** /**
* Logout from the provider, removes the token if possible * Logout from the provider, removes the token if possible

View File

@@ -69,14 +69,16 @@ class Twitter extends AbstractProvider
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate(array $params = array())
{ {
$params = array_merge(['providerId' => $this->getId()], $params);
$code = $this->twitter->request( $code = $this->twitter->request(
'POST', 'POST',
$this->twitter->url('oauth/request_token', ''), $this->twitter->url('oauth/request_token', ''),
['oauth_callback' => $this->generator->generate( ['oauth_callback' => $this->generator->generate(
'login_authentication_provider_callback', 'login_authentication_provider_callback',
['providerId' => $this->getId()], $params,
UrlGenerator::ABSOLUTE_URL UrlGenerator::ABSOLUTE_URL
)] )]
); );

View File

@@ -79,8 +79,10 @@ class Viadeo extends AbstractProvider
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate() public function authenticate(array $params = array())
{ {
$params = array_merge(['providerId' => $this->getId()], $params);
$state = $this->createState(); $state = $this->createState();
$this->session->set('viadeo.provider.state', $state); $this->session->set('viadeo.provider.state', $state);
@@ -91,7 +93,7 @@ class Viadeo extends AbstractProvider
'response_type' => 'code', 'response_type' => 'code',
'redirect_uri' => $this->generator->generate( 'redirect_uri' => $this->generator->generate(
'login_authentication_provider_callback', 'login_authentication_provider_callback',
['providerId' => $this->getId()], $params,
UrlGenerator::ABSOLUTE_URL UrlGenerator::ABSOLUTE_URL
), ),
])); ]));

View File

@@ -10,9 +10,9 @@ use Alchemy\Phrasea\Core\Configuration\RegistrationManager;
use Alchemy\Phrasea\Core\Event\RegistrationEvent; use Alchemy\Phrasea\Core\Event\RegistrationEvent;
use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Model\Entities\Registration;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\UsrAuthProvider; use Alchemy\Phrasea\Model\Entities\UsrAuthProvider;
use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator; use Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Alchemy\Phrasea\Model\Manipulator\UserManipulator; use Alchemy\Phrasea\Model\Manipulator\UserManipulator;
@@ -176,7 +176,7 @@ class RegistrationService
); );
if ($userAuthenticationProvider) { if ($userAuthenticationProvider) {
return $userAuthenticationProvider->getUser($this->app); return $userAuthenticationProvider->getUser();
} }
return null; return null;
@@ -190,8 +190,7 @@ class RegistrationService
$provider = $this->oauthProviderCollection->get($providerId); $provider = $this->oauthProviderCollection->get($providerId);
} }
$inscriptions = $this->registrationManager->getRegistrationSummary(); $authorizedCollections = $this->getAuthorizedCollections($selectedCollections);
$authorizedCollections = $this->getAuthorizedCollections($selectedCollections, $inscriptions);
if (!isset($data['login'])) { if (!isset($data['login'])) {
$data['login'] = $data['email']; $data['login'] = $data['email'];
@@ -205,7 +204,7 @@ class RegistrationService
foreach (self::$userPropertySetterMap as $property => $method) { foreach (self::$userPropertySetterMap as $property => $method) {
if (isset($data[$property])) { if (isset($data[$property])) {
call_user_func(array($user, $method), $data[$property]); $user->$method($data[$property]);
} }
} }
@@ -217,7 +216,17 @@ class RegistrationService
$this->entityManager->flush(); $this->entityManager->flush();
} }
$this->applyAclsToUser($authorizedCollections, $user); if ($this->configuration->get(['registry', 'registration', 'auto-register-enabled'])) {
$acl = $this->aclProvider->get($user);
foreach ($authorizedCollections as $baseId => $collection) {
if( ($model = $collection->getAutoregisterModel($data['email'])) !== null) {
if( ($template_user = $this->userRepository->findByLogin($model)) !== null) {
$acl->apply_model($template_user, [$baseId]);
}
}
}
}
$this->createCollectionAccessDemands($user, $authorizedCollections); $this->createCollectionAccessDemands($user, $authorizedCollections);
$user->setMailLocked(true); $user->setMailLocked(true);
@@ -226,8 +235,7 @@ class RegistrationService
public function createCollectionRequests(User $user, array $collections) public function createCollectionRequests(User $user, array $collections)
{ {
$inscriptions = $this->registrationManager->getRegistrationSummary($user); $authorizedCollections = $this->getAuthorizedCollections($collections);
$authorizedCollections = $this->getAuthorizedCollections($collections, $inscriptions);
$this->createCollectionAccessDemands($user, $authorizedCollections); $this->createCollectionAccessDemands($user, $authorizedCollections);
} }
@@ -279,7 +287,7 @@ class RegistrationService
/** /**
* @param array $selectedCollections * @param array $selectedCollections
* @return array * @return \collection[]
*/ */
private function getAuthorizedCollections(array $selectedCollections = null) private function getAuthorizedCollections(array $selectedCollections = null)
{ {
@@ -292,8 +300,8 @@ class RegistrationService
continue; continue;
} }
if ($canRegister = \igorw\get_in($inscriptions, [$databox->get_sbas_id(), 'config', 'collections', $collection->get_base_id(), 'can-register'])) { if (\igorw\get_in($inscriptions, [$databox->get_sbas_id(), 'config', 'collections', $collection->get_base_id(), 'can-register'])) {
$authorizedCollections[$collection->get_base_id()] = $canRegister; $authorizedCollections[$collection->get_base_id()] = $collection;
} }
} }
} }
@@ -301,42 +309,41 @@ class RegistrationService
return $authorizedCollections; return $authorizedCollections;
} }
/**
* @param $authorizedCollections
* @param $user
* @return mixed
* @throws \Exception
*/
private function applyAclsToUser(array $authorizedCollections, User $user)
{
$acl = $this->aclProvider->get($user);
if ($this->configuration->get(['registry', 'registration', 'auto-register-enabled'])) {
$template_user = $this->userRepository->findByLogin(User::USER_AUTOREGISTER);
$acl->apply_model($template_user, array_keys($authorizedCollections));
}
}
/** /**
* @param User $user * @param User $user
* @param array $authorizedCollections * @param array $authorizedCollections
*/ */
private function createCollectionAccessDemands(User $user, $authorizedCollections) private function createCollectionAccessDemands(User $user, $authorizedCollections)
{ {
$successfulRegistrations = [];
$acl = $this->aclProvider->get($user); $acl = $this->aclProvider->get($user);
$autoReg = $acl->get_granted_base();
$registrationManipulator = $this->registrationManipulator; $registrationManipulator = $this->registrationManipulator;
$successfulRegistrations = [];
array_walk($authorizedCollections, function ($authorization, $baseId) use ($registrationManipulator, $user, &$successfulRegistrations, $acl) { foreach($authorizedCollections as $baseId => $collection) {
if (false === $authorization || $acl->has_access_to_base($baseId)) { if(!$acl->has_access_to_base($baseId)) {
return; $registrationManipulator->createRegistration($user, $collection);
$successfulRegistrations[$baseId] = $collection;
} }
}
$autoReg = $acl->get_granted_base();
$granted = [];
foreach ($autoReg as $baseId => $collection) {
$granted[$baseId] = $collection->get_label($this->app['locale']);
}
if(count($granted) > 0) {
$this->app['manipulator.webhook-event']->create(
WebhookEvent::USER_REGISTRATION_GRANTED,
WebhookEvent::USER_REGISTRATION_TYPE,
[
'user_id' => $user->getId(),
'granted' => $granted,
'rejected' => []
]
);
}
$collection = \collection::getByBaseId($this->app, $baseId);
$registrationManipulator->createRegistration($user, $collection);
$successfulRegistrations[$baseId] = $collection;
});
$this->eventDispatcher->dispatch(PhraseaEvents::REGISTRATION_AUTOREGISTER, new RegistrationEvent($user, $autoReg)); $this->eventDispatcher->dispatch(PhraseaEvents::REGISTRATION_AUTOREGISTER, new RegistrationEvent($user, $autoReg));
$this->eventDispatcher->dispatch(PhraseaEvents::REGISTRATION_CREATE, new RegistrationEvent($user, $successfulRegistrations)); $this->eventDispatcher->dispatch(PhraseaEvents::REGISTRATION_CREATE, new RegistrationEvent($user, $successfulRegistrations));

View File

@@ -14,15 +14,18 @@ namespace Alchemy\Phrasea\Border\Checker;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Border\File; use Alchemy\Phrasea\Border\File;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use MediaVorus\Media\Document;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
class Colorspace extends AbstractChecker class Colorspace extends AbstractChecker
{ {
protected $colorspaces; protected $colorspaces;
protected $mediatypes;
const COLORSPACE_RGB = 'rgb'; const COLORSPACE_RGB = 'rgb';
const COLORSPACE_CMYK = 'cmyk'; const COLORSPACE_CMYK = 'cmyk';
const COLORSPACE_GRAYSCALE = 'grayscale'; const COLORSPACE_GRAYSCALE = 'grayscale';
const COLORSPACE_RGBA = 'rgba';
public function __construct(Application $app, array $options) public function __construct(Application $app, array $options)
{ {
@@ -30,7 +33,12 @@ class Colorspace extends AbstractChecker
throw new \InvalidArgumentException('Missing "colorspaces" options'); throw new \InvalidArgumentException('Missing "colorspaces" options');
} }
if (!isset($options['media_types'])) {
throw new \InvalidArgumentException('Missing "media_types" options');
}
$this->colorspaces = array_map('strtolower', (array) $options['colorspaces']); $this->colorspaces = array_map('strtolower', (array) $options['colorspaces']);
$this->mediatypes = $options['media_types'];
parent::__construct($app); parent::__construct($app);
} }
@@ -40,6 +48,8 @@ class Colorspace extends AbstractChecker
if (0 === count($this->colorspaces)) { if (0 === count($this->colorspaces)) {
$boolean = true; //bypass color if empty array $boolean = true; //bypass color if empty array
} elseif (0 !== count($this->mediatypes) && $file->getMedia()->getType() !== NULL && !in_array($file->getMedia()->getType(), $this->mediatypes)) {
$boolean = true; //bypass color checker if media type is not in the config
} elseif (method_exists($file->getMedia(), 'getColorSpace')) { } elseif (method_exists($file->getMedia(), 'getColorSpace')) {
$colorspace = null; $colorspace = null;
switch ($file->getMedia()->getColorSpace()) switch ($file->getMedia()->getColorSpace())
@@ -54,6 +64,9 @@ class Colorspace extends AbstractChecker
case \MediaVorus\Media\Image::COLORSPACE_GRAYSCALE: case \MediaVorus\Media\Image::COLORSPACE_GRAYSCALE:
$colorspace = self::COLORSPACE_GRAYSCALE; $colorspace = self::COLORSPACE_GRAYSCALE;
break; break;
case \MediaVorus\Media\Image::COLORSPACE_RGBA:
$colorspace = self::COLORSPACE_RGBA;
break;
} }
$boolean = $colorspace !== null && in_array(strtolower($colorspace), $this->colorspaces); $boolean = $colorspace !== null && in_array(strtolower($colorspace), $this->colorspaces);

View File

@@ -49,6 +49,7 @@ class File
protected $originalName; protected $originalName;
protected $md5; protected $md5;
protected $attributes; protected $attributes;
public static $xmpTag = ['XMP-xmpMM:DocumentID'];
/** /**
* Constructor * Constructor
@@ -102,6 +103,7 @@ class File
'IPTC:UniqueDocumentID', 'IPTC:UniqueDocumentID',
'ExifIFD:ImageUniqueID', 'ExifIFD:ImageUniqueID',
'Canon:ImageUniqueID', 'Canon:ImageUniqueID',
'XMP-xmpMM:DocumentID',
]; ];
if (!$this->uuid) { if (!$this->uuid) {
@@ -112,6 +114,9 @@ class File
foreach ($availableUUIDs as $meta) { foreach ($availableUUIDs as $meta) {
if ($metadatas->containsKey($meta)) { if ($metadatas->containsKey($meta)) {
$candidate = $metadatas->get($meta)->getValue()->asString(); $candidate = $metadatas->get($meta)->getValue()->asString();
if(in_array($meta, self::$xmpTag)){
$candidate = self::sanitizeXmpUuid($candidate);
}
if (Uuid::isValid($candidate)) { if (Uuid::isValid($candidate)) {
$uuid = $candidate; $uuid = $candidate;
break; break;
@@ -287,4 +292,13 @@ class File
return new File($app, $media, $collection, $originalName); return new File($app, $media, $collection, $originalName);
} }
/**
* Sanitize XMP UUID
* @param $uuid
* @return mixed
*/
public static function sanitizeXmpUuid($uuid){
return str_replace('xmp.did:', '', $uuid);
}
} }

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\Command\Databox; namespace Alchemy\Phrasea\Command\Databox;
use Alchemy\Phrasea\Command\Command; use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Core\Configuration\StructureTemplate;
use Alchemy\Phrasea\Databox\DataboxConnectionSettings; use Alchemy\Phrasea\Databox\DataboxConnectionSettings;
use Alchemy\Phrasea\Databox\DataboxService; use Alchemy\Phrasea\Databox\DataboxService;
use Alchemy\Phrasea\Model\Repositories\UserRepository; use Alchemy\Phrasea\Model\Repositories\UserRepository;
@@ -10,6 +11,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\DialogHelper;
class CreateDataboxCommand extends Command class CreateDataboxCommand extends Command
{ {
@@ -18,7 +20,7 @@ class CreateDataboxCommand extends Command
{ {
$this->setName('databox:create') $this->setName('databox:create')
->addArgument('databox', InputArgument::REQUIRED, 'Database name for the databox', null) ->addArgument('databox', InputArgument::REQUIRED, 'Database name for the databox', null)
->addArgument('owner', InputArgument::REQUIRED, 'Email of the databox admin user', null) ->addArgument('owner', InputArgument::REQUIRED, 'Login of the databox admin user', null)
->addOption('connection', 'c', InputOption::VALUE_NONE, 'Flag to set new database settings') ->addOption('connection', 'c', InputOption::VALUE_NONE, 'Flag to set new database settings')
->addOption('db-host', null, InputOption::VALUE_OPTIONAL, 'MySQL server host', 'localhost') ->addOption('db-host', null, InputOption::VALUE_OPTIONAL, 'MySQL server host', 'localhost')
->addOption('db-port', null, InputOption::VALUE_OPTIONAL, 'MySQL server port', 3306) ->addOption('db-port', null, InputOption::VALUE_OPTIONAL, 'MySQL server port', 3306)
@@ -28,13 +30,16 @@ class CreateDataboxCommand extends Command
'db-template', 'db-template',
null, null,
InputOption::VALUE_OPTIONAL, InputOption::VALUE_OPTIONAL,
'Metadata structure language template (available are fr (french) and en (english))', 'Databox template',
'fr' null
); );
} }
protected function doExecute(InputInterface $input, OutputInterface $output) protected function doExecute(InputInterface $input, OutputInterface $output)
{ {
/** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog');
$databoxName = $input->getArgument('databox'); $databoxName = $input->getArgument('databox');
$connectionSettings = $input->getOption('connection') == false ? null : new DataboxConnectionSettings( $connectionSettings = $input->getOption('connection') == false ? null : new DataboxConnectionSettings(
$input->getOption('db-host'), $input->getOption('db-host'),
@@ -45,18 +50,63 @@ class CreateDataboxCommand extends Command
/** @var UserRepository $userRepository */ /** @var UserRepository $userRepository */
$userRepository = $this->container['repo.users']; $userRepository = $this->container['repo.users'];
$owner = $userRepository->findByLogin($input->getArgument('owner'));
if(!$owner) {
$output->writeln(sprintf("<error>Unknown user \"%s\"</error>", $input->getArgument('owner')));
return 1;
}
/** @var DataboxService $databoxService */ /** @var DataboxService $databoxService */
$databoxService = $this->container['databox.service']; $databoxService = $this->container['databox.service'];
$owner = $userRepository->findByEmail($input->getArgument('owner')); if($databoxService->exists($databoxName, $connectionSettings)) {
$output->writeln(sprintf("<error>Database \"%s\" already exists</error>", $databoxName));
$databoxService->createDatabox( return 1;
$databoxName, }
$input->getOption('db-template') . '-simple',
$owner, /** @var StructureTemplate $templates */
$connectionSettings $templates = $this->container['phraseanet.structure-template'];
);
// if a template name is provided, check that this template exists
$templateName = $input->getOption('db-template');
if($templateName && !$templates->getByName($templateName)) {
throw new \Exception_InvalidArgument(sprintf("Databox template \"%s\" not found.", $templateName));
}
if(!$templateName) {
// propose a default template : the first available if "en-simple" does not exists.
$defaultDBoxTemplate = $templates->getDefault();
do {
$templateName = $dialog->ask($output, 'Choose a template from ('.$templates->toString().') for metadata structure <comment>[default: "'.$defaultDBoxTemplate.'"]</comment> : ', $defaultDBoxTemplate);
if(!$templates->getByName($templateName)){
$output->writeln(" <error>Data-Box template : Template not found, try again.</error>");
}
}
while (!$templates->getByName($templateName));
}
try {
$databoxService->createDatabox(
$databoxName,
$templateName,
$owner,
$connectionSettings
);
}
catch(\Exception $e) {
$output->writeln(sprintf("<error>Failed to create database \"%s\", error=\"%s\"</error>"
, $databoxName
, $e->getMessage()
));
return 1;
}
$output->writeln('Databox created'); $output->writeln('Databox created');
return 0;
} }
} }

View File

@@ -13,6 +13,8 @@ namespace Alchemy\Phrasea\Command\Developer;
use Alchemy\Phrasea\Command\Command; use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Core\Version; use Alchemy\Phrasea\Core\Version;
use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Utilities\StringHelper;
use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -112,11 +114,15 @@ class IniReset extends Command
// get data paths // get data paths
$dataPath = $this->container['conf']->get(['main', 'storage', 'subdefs'], $this->container['root.path'].'/datas'); $dataPath = $this->container['conf']->get(['main', 'storage', 'subdefs'], $this->container['root.path'].'/datas');
$schema = $this->container['orm.em']->getConnection()->getSchemaManager(); /** @var Connection $connection */
$connection = $this->container['orm.em']->getConnection();
$schema = $connection->getSchemaManager();
$output->writeln('Creating database "'.$dbs['ab'].'"...<info>OK</info>'); $output->writeln('Creating database "'.$dbs['ab'].'"...<info>OK</info>');
$schema->dropAndCreateDatabase($dbs['ab']); $schema->dropAndCreateDatabase(StringHelper::SqlQuote($dbs['ab'], StringHelper::SQL_IDENTIFIER));
$output->writeln('Creating database "'.$dbName.'"...<info>OK</info>'); $output->writeln('Creating database "'.$dbName.'"...<info>OK</info>');
$schema->dropAndCreateDatabase($dbName); $schema->dropAndCreateDatabase(StringHelper::SqlQuote($dbName, StringHelper::SQL_IDENTIFIER));
// inject v3.1 fixtures // inject v3.1 fixtures
if ($input->getOption('run-patches')) { if ($input->getOption('run-patches')) {
@@ -212,7 +218,7 @@ class IniReset extends Command
} else { } else {
$output->write(sprintf('Upgrading... from version <info>%s</info> to <info>%s</info>', $this->app->getApplicationBox()->get_version(), $version->getNumber()), true); $output->write(sprintf('Upgrading... from version <info>%s</info> to <info>%s</info>', $this->app->getApplicationBox()->get_version(), $version->getNumber()), true);
} }
$cmd = 'php ' . __DIR__ . '/../../../../../bin/setup system:upgrade -y -f -v'; $cmd = 'php ' . __DIR__ . '/../../../../../bin/setup system:upgrade -y -f -v';
$process = new Process($cmd); $process = new Process($cmd);
$process->setTimeout(600); $process->setTimeout(600);

View File

@@ -31,7 +31,7 @@ class JsFixtures extends Command
protected function doExecute(InputInterface $input, OutputInterface $output) protected function doExecute(InputInterface $input, OutputInterface $output)
{ {
if (!file_exists($this->container['db.fixture.info']['path'])) { if (!file_exists($this->container['db.fixture.info']['path'])) {
throw new RuntimeException('You must generate sqlite db first, run "bin/console phraseanet:regenerate-sqlite" command.'); throw new RuntimeException('You must generate sqlite db first, run "bin/developer phraseanet:regenerate-sqlite" command.');
} }
$this->container['orm.em'] = $this->container->extend('orm.em', function($em, $app) { $this->container['orm.em'] = $this->container->extend('orm.em', function($em, $app) {

View File

@@ -432,8 +432,8 @@ class RegenerateSqliteDb extends Command
if ($i < 3) { if ($i < 3) {
/** @var SubdefSubstituer $substituer */ /** @var SubdefSubstituer $substituer */
$substituer = $this->container['subdef.substituer']; $substituer = $this->container['subdef.substituer'];
$substituer->substitute($story, 'preview', $media); $substituer->substituteSubdef($story, 'preview', $media);
$substituer->substitute($story, 'thumbnail', $media); $substituer->substituteSubdef($story, 'thumbnail', $media);
} }
$DI['record_story_' . $i] = $story; $DI['record_story_' . $i] = $story;
} }

View File

@@ -13,6 +13,8 @@ namespace Alchemy\Phrasea\Command\Developer;
use Alchemy\Phrasea\Command\Command; use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Utilities\StringHelper;
use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -44,20 +46,30 @@ class SetupTestsDbs extends Command
$dbs[] = $settings['database']['ab_name']; $dbs[] = $settings['database']['ab_name'];
$dbs[] = $settings['database']['db_name']; $dbs[] = $settings['database']['db_name'];
$schema = $this->container['orm.em']->getConnection()->getSchemaManager(); /** @var Connection $connection */
$connection = $this->container['orm.em']->getConnection();
$schema = $connection->getSchemaManager();
foreach($dbs as $name) { foreach($dbs as $name) {
$output->writeln('Creating database "'.$name.'"...<info>OK</info>'); $output->writeln('Creating database "'.$name.'"...<info>OK</info>');
$name = StringHelper::SqlQuote($name, StringHelper::SQL_IDENTIFIER); // quote as `identifier`
$schema->dropAndCreateDatabase($name); $schema->dropAndCreateDatabase($name);
} }
$this->container['orm.em']->getConnection()->executeUpdate(' $user = StringHelper::SqlQuote($settings['database']['user'], StringHelper::SQL_VALUE); // quote as 'value'
GRANT ALL PRIVILEGES ON '.$settings['database']['ab_name'].'.* TO \''.$settings['database']['user'].'\'@\''.$settings['database']['host'].'\' IDENTIFIED BY \''.$settings['database']['password'].'\' WITH GRANT OPTION $host = StringHelper::SqlQuote($settings['database']['host'], StringHelper::SQL_VALUE);
'); $pass = StringHelper::SqlQuote($settings['database']['password'], StringHelper::SQL_VALUE);
$this->container['orm.em']->getConnection()->executeUpdate(' $ab_name = StringHelper::SqlQuote($settings['database']['ab_name'], StringHelper::SQL_IDENTIFIER);
GRANT ALL PRIVILEGES ON '.$settings['database']['db_name'].'.* TO \''.$settings['database']['user'].'\'@\''.$settings['database']['host'].'\' IDENTIFIED BY \''.$settings['database']['password'].'\' WITH GRANT OPTION $db_name = StringHelper::SqlQuote($settings['database']['db_name'], StringHelper::SQL_IDENTIFIER);
');
$this->container['orm.em']->getConnection()->executeUpdate(
'GRANT ALL PRIVILEGES ON '.$ab_name.'.* TO '.$user.'@'.$host.' IDENTIFIED BY '.$pass.' WITH GRANT OPTION'
);
$this->container['orm.em']->getConnection()->executeUpdate(
'GRANT ALL PRIVILEGES ON '.$db_name.'.* TO '.$user.'@'.$host.' IDENTIFIED BY '.$pass.' WITH GRANT OPTION'
);
$this->container['orm.em']->getConnection()->executeUpdate('SET @@global.sql_mode= ""'); $this->container['orm.em']->getConnection()->executeUpdate('SET @@global.sql_mode= ""');

View File

@@ -24,7 +24,7 @@ class IndexCreateCommand extends Command
{ {
$this $this
->setName('searchengine:index:create') ->setName('searchengine:index:create')
->setDescription('Creates search index') ->setDescription('Creates search index <fg=yellow;>(Deprecated use searchengine:index instead)</>')
->addOption('drop', 'd', InputOption::VALUE_NONE, 'Drops the index if it already exists.'); ->addOption('drop', 'd', InputOption::VALUE_NONE, 'Drops the index if it already exists.');
} }

View File

@@ -23,12 +23,12 @@ class IndexDropCommand extends Command
{ {
$this $this
->setName('searchengine:index:drop') ->setName('searchengine:index:drop')
->setDescription('Deletes the search index') ->setDescription('Deletes the search index <fg=yellow;>(Deprecated use searchengine:index instead)</>')
->addOption( ->addOption(
'force', 'force',
null, null,
InputOption::VALUE_NONE, InputOption::VALUE_NONE,
"Don't ask for for the dropping of the index, but force the operation to run." "Don't ask for the dropping of the index, but force the operation to run."
) )
; ;
} }

View File

@@ -0,0 +1,215 @@
<?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\SearchEngine;
use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class IndexManipulateCommand extends Command
{
/** @var OutputInterface */
private $output = null;
/**
* print a string if verbosity >= verbose (-v)
*
* @param string $s
* @param int $verbosity
*/
private function verbose($s, $verbosity = OutputInterface::VERBOSITY_VERBOSE)
{
if ($this->output->getVerbosity() >= $verbosity) {
$this->output->writeln($s);
}
}
protected function configure()
{
$this
->setName('searchengine:index')
->setDescription('Manipulates search index')
->addOption('drop', 'd', InputOption::VALUE_NONE, 'Drops the index.')
->addOption('create', 'c', InputOption::VALUE_NONE, 'Creates the index.')
->addOption('populate', 'p', InputOption::VALUE_NONE, 'Populates the index.')
->addOption('temporary', 't', InputOption::VALUE_NONE, 'Populates using temporary index.')
->addOption('name', null, InputOption::VALUE_REQUIRED, 'index name', null)
->addOption('host', null, InputOption::VALUE_REQUIRED, 'host', null)
->addOption('port', null, InputOption::VALUE_REQUIRED, 'port', null)
->addOption('order', null, InputOption::VALUE_REQUIRED, 'order (record_id|modification_date)[.asc|.desc]', null)
->addOption(
'databox_id',
null,
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Only populate chosen databox'
)->addOption(
'force',
null,
InputOption::VALUE_NONE,
"Don't ask for for the dropping of the index, but force the operation to run."
);
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
/** @var Indexer $indexer */
$indexer = $this->container['elasticsearch.indexer'];
/** @var ElasticsearchOptions $options */
$options = $indexer->getIndex()->getOptions();
if($input->getOption('name')) {
$options->setIndexName($input->getOption('name'));
}
if($input->getOption('host')) {
$options->setHost($input->getOption('host'));
}
if($input->getOption('port')) {
$options->setPort($input->getOption('port'));
}
if($input->getOption('order')) {
$order = explode('.', $input->getOption('order'));
if (!$options->setPopulateOrder($order[0])) {
$output->writeln(sprintf('<error>bad order value for --order</error>'));
return 1;
}
if (count($order) > 1) {
if (!$options->setPopulateDirection($order[1])) {
$output->writeln(sprintf('<error>bad direction value for --order</error>'));
return 1;
}
}
}
$idx = sprintf("%s@%s:%s", $options->getIndexName(), $options->getHost(), $options->getPort());
$drop = $input->getOption('drop');
$create = $input->getOption('create');
$populate = $input->getOption('populate');
$temporary = $input->getOption('temporary');
$databoxes_id = $input->getOption('databox_id');
if($temporary && (!$populate || $databoxes_id)) {
$output->writeln(sprintf('<error>temporary must be used to populate all databoxes</error>', $idx));
return 1;
}
$indexExists = $indexer->indexExists();
if ($drop && $indexExists) {
if ($input->getOption('force')) {
$confirmation = true;
}
else {
$question = '<question>You are about to delete the index and all contained data. Are you sure you wish to continue? (y/n)</question>';
$confirmation = $this->getHelper('dialog')->askConfirmation($output, $question, false);
}
if ($confirmation) {
$indexer->deleteIndex();
$this->verbose(sprintf('<info>Search index "%s" was dropped.</info>', $idx));
}
else {
$this->verbose('Canceled.');
return 0;
}
}
$indexExists = $indexer->indexExists();
if ($create) {
if($indexExists) {
$output->writeln(sprintf('<error>The search index "%s" already exists.</error>', $idx));
return 1;
}
else {
$r = $indexer->createIndex();
$this->verbose(sprintf('<info>Search index "%s@%s:%s" -> "%s" was created</info>'
, $r['alias']
, $options->getHost()
, $options->getPort()
, $r['index']
));
}
}
$indexExists = $indexer->indexExists();
if($populate) {
if(!$indexExists) {
$r = $indexer->createIndex();
$this->verbose(sprintf('<info>Search index "%s@%s:%s" -> "%s" was created</info>'
, $r['alias']
, $options->getHost()
, $options->getPort()
, $r['index']
));
}
$oldAliasName = $indexer->getIndex()->getName();
$newAliasName = $newIndexName = null;
if($temporary) {
// change the name to create a new index
$now = explode(' ', microtime());
$now = sprintf("%X%X", $now[1], 1000000*$now[0]);
$indexer->getIndex()->getOptions()->setIndexName($oldAliasName . "_T" . $now);
$r = $indexer->createIndex($oldAliasName);
$newIndexName = $r['index'];
$newAliasName = $r['alias'];
$this->verbose(sprintf('<info>Temporary index "%s@%s:%s" -> "%s" was created</info>'
, $r['alias']
, $options->getHost()
, $options->getPort()
, $r['index']
));
}
foreach ($this->container->getDataboxes() as $databox) {
if (!$databoxes_id || in_array($databox->get_sbas_id(), $databoxes_id)) {
$r = $indexer->populateIndex(Indexer::THESAURUS | Indexer::RECORDS, $databox, false); // , $temporary);
$output->writeln(sprintf(
"Indexation of databox \"%s\" finished in %0.2f sec (Mem. %0.2f Mo)",
$databox->get_dbname(),
$r['duration']/1000,
$r['memory']/1048576)
);
}
}
if($temporary) {
$this->verbose('<info>Renaming temporary :</info>');
$indexer->getIndex()->getOptions()->setIndexName($oldAliasName);
$r = $indexer->replaceIndex($newIndexName, $newAliasName);
foreach($r as $action) {
$this->verbose(sprintf(' <info>%s</info>', $action['msg']));
}
}
}
return 0;
}
}

View File

@@ -23,7 +23,7 @@ class IndexPopulateCommand extends Command
{ {
$this $this
->setName('searchengine:index:populate') ->setName('searchengine:index:populate')
->setDescription('Populate search index') ->setDescription('Populate search index <fg=yellow;>(Deprecated use searchengine:index instead)</>')
->addOption( ->addOption(
'thesaurus', 'thesaurus',
null, null,

View File

@@ -0,0 +1,381 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 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 Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class FixAutoincrements extends Command
{
/** @var InputInterface */
private $input;
/** @var OutputInterface */
private $output;
/** @var \appbox */
private $appBox;
/** @var \databox[] */
private $databoxes;
public function __construct($name = null)
{
parent::__construct($name);
$this
->setDescription("Fix autoincrements")
->addOption('dry', null, InputOption::VALUE_NONE, 'Dry run : list but do not update.');
return $this;
}
/**
* {@inheritdoc}
*/
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
$this->appBox = $this->getContainer()->getApplicationBox();
$this->databoxes = [];
foreach ($this->getContainer()->getDataboxes() as $databox) {
$this->databoxes[] = $databox;
}
$this
->ab_fix_bas()
->ab_fix_BasketElements()
->ab_fix_Baskets()
->ab_fix_Sessions()
->db_fix_coll()
->db_fix_record()
;
}
/**
* @param \Appbox|\databox $box
* @param $table
* @param $subtables
*/
private function box_fixTable($box, $table, $subtables)
{
$ouputTable = new Table($this->output);
$title = sprintf("fixing table \"%s.%s\"", $box->get_dbname(), $table);
$ouputTable->setHeaders([new TableCell($title, ['colspan'=>2])]);
$max_id = $this->box_getMax($box, $table, $subtables, $ouputTable);
//if($this->output->getVerbosity())
$this->box_setAutoIncrement($box, $table, $max_id, $ouputTable);
$ouputTable->render();
$this->output->writeln("");
}
/**
* @param \Appbox|\databox $box
* @param $table
* @param $subtables
* @param Table $ouputTable
* @return int
*/
private function box_getMax($box, $table, $subtables, $ouputTable)
{
$sql = " SELECT '".$box->get_dbname().'.'.$table.".AUTO_INCREMENT' AS src, AUTO_INCREMENT AS `id` FROM information_schema.TABLES\n"
. " WHERE TABLE_SCHEMA = :dbname AND TABLE_NAME = '" . $table . "'\n";
foreach ($subtables as $subtable => $fieldname) {
$sql .=
" UNION\n"
. " SELECT '".$box->get_dbname().'.'.$subtable.'.'.$fieldname."' AS src, MAX(`" . $fieldname . "`)+1 AS `id` FROM `" . $subtable . "`\n";
}
$stmt = $box->get_connection()->executeQuery($sql, [':dbname' => $box->get_dbname()]);
$max_id = 0;
$rows = $stmt->fetchAll();
foreach($rows as $row) {
$id = $row['id'];
$ouputTable->addRow([$row['src'], is_null($id) ? "null" : $id]);
if(!is_null($id)){
$id = (int)$id;
if ($id > $max_id) {
$max_id = $id;
}
}
}
$stmt->closeCursor();
return $max_id;
}
/**
* @param \Appbox|\databox $box
* @param $table
* @param $max_id
* @param Table $ouputTable
* @throws \Doctrine\DBAL\DBALException
*/
private function box_setAutoIncrement($box, $table, $max_id, $ouputTable)
{
$sql = "ALTER TABLE `" . $table . "` AUTO_INCREMENT = " . $max_id;
$msg = [
sprintf("%s.%s.AUTO_INCREMENT set to", $box->get_dbname(), $table),
$max_id,
];
$this->box_setSQL($box, $sql, [], $msg, $ouputTable);
}
/**
* @param \Appbox|\databox $box
* @param string $sql
* @param array $parms
* @param string $msg[]
* @param Table $ouputTable
*/
private function box_setSQL($box, $sql, $parms, $msg, $ouputTable)
{
$ouputTable->addRow(New TableSeparator());
if($this->input->getOption("dry")) {
$msg[1] = sprintf("<comment>%s</comment> (dry-run : not done)", $msg[1]);
$ouputTable->addRow($msg);
}
else {
try {
$stmt = $box->get_connection()->executeQuery($sql, $parms);
$stmt->closeCursor();
$msg[1] = sprintf("<info>%s</info>", $msg[1]);
$ouputTable->addRow($msg);
}
catch(\Exception $e) {
$msg[1] = sprintf("<error>%s</error>)", $msg[1]);
$ouputTable->addRow($msg);
}
}
}
//
//
//============================= Databoxes =======================
//
//
/**
* fix every Databox "record" autoincrement
*
* @return $this
* @throws \Doctrine\DBAL\DBALException
*/
private function db_fix_coll()
{
foreach ($this->databoxes as $databox) {
$this->box_fixTable(
$databox,
'coll',
[
'collusr' => 'coll_id',
'log_colls' => 'coll_id',
]
);
}
return $this;
}
/**
* fix every Databox "record" autoincrement
*
* @return $this
* @throws \Doctrine\DBAL\DBALException
*/
private function db_fix_record()
{
foreach ($this->databoxes as $databox) {
$this->box_fixTable(
$databox,
'record',
[
'log_docs' => 'record_id',
'log_view' => 'record_id',
'record' => 'record_id',
]
);
}
return $this;
}
//
//
//============================= ApplicationBox =======================
//
//
/**
* fix AppBox "bas" autoincrement
*
* @return $this
* @throws \Doctrine\DBAL\DBALException
*/
private function ab_fix_bas()
{
$this->box_fixTable(
$this->appBox,
'bas',
[
'basusr' => 'base_id',
'demand' => 'base_id',
'Feeds' => 'base_id',
'FtpExportElements' => 'base_id',
'LazaretFiles' => 'base_id',
'OrderElements' => 'base_id',
'Registrations' => 'base_id',
]
);
return $this;
}
/**
* fix AppBox "BasketElements" autoincrement
*
* @return $this
* @throws \Doctrine\DBAL\DBALException
*/
private function ab_fix_BasketElements()
{
$this->box_fixTable(
$this->appBox,
'BasketElements',
[
'ValidationDatas' => 'basket_element_id',
]
);
return $this;
}
/**
* fix AppBox "Baskets" autoincrement
*
* @return $this
* @throws \Doctrine\DBAL\DBALException
*/
private function ab_fix_Baskets()
{
$this->box_fixTable(
$this->appBox,
'Baskets',
[
'BasketElements' => 'basket_id',
'Orders' => 'basket_id',
'ValidationSessions' => 'basket_id',
]
);
return $this;
}
/**
* fix AppBox "Sessions" autoincrement
*
* @return $this
* @throws \Doctrine\DBAL\DBALException
*/
private function ab_fix_Sessions()
{
$ouputTable = new Table($this->output);
$title = sprintf("fixing table \"%s.%s\"", $this->appBox->get_dbname(), "Sessions");
$ouputTable->setHeaders([new TableCell($title, ['colspan'=>2])]);
$site = $this->getContainer()['conf']->get(['main', 'key']);
// if autoincrement, get the current value as a minimum
$max_SessionId = $this->box_getMax(
$this->appBox,
'Sessions',
[], // no sub-tables
$ouputTable
);
// get max session from databoxes, using the "log" table which refers to ab.Sessions ids
foreach ($this->databoxes as $databox) {
$db = $databox->get_connection();
$sql = "SELECT MAX(`sit_session`) FROM `log` WHERE `site` = :site";
$stmt = $db->executeQuery($sql, [':site' => $site]);
$id = $stmt->fetchColumn(0);
$ouputTable->addRow([sprintf("%s.log.sit_session", $databox->get_dbname()), sprintf("%s", is_null($id) ? 'null' : $id)]);
if(!is_null($id)) {
$id = (int)$id + 1;
if ($id > $max_SessionId) {
$max_SessionId = $id;
}
}
$stmt->closeCursor();
}
// fix using different methods
foreach ([
// 4.0 with autoincrement
[
'sql' => "ALTER TABLE `Sessions` AUTO_INCREMENT = " . $max_SessionId, // can't use parameter here
'parms' => [],
'msg' => ["ab.Sessions.AUTO_INCREMENT set to", $max_SessionId],
],
/* --- custon generators not yet implemented in phraseanet
// 4.0 with custom generator already set
[
'sql' => "UPDATE `Id` SET value = :v WHERE `id` = :k",
'parms' => [':v' => $max_SessionId, ':k' => 'Alchemy\\Phrasea\\Model\\Entities\\Session'],
'msg' => ["ab.Id['Sessions']' set to", $max_SessionId],
],
// 4.0 with custom generator not yet set
[
'sql' => "INSERT INTO `Id` (`id`, `value`) VALUES (:k, :v)",
'parms' => [':v' => $max_SessionId, ':k' => 'Alchemy\\Phrasea\\Model\\Entities\\Session'],
'msg' => ["ab.Id['Sessions']' set to", $max_SessionId],
]
--- */
] as $sql) {
// one or more sql will fail, no pb
$this->box_setSQL($this->appBox, $sql['sql'], $sql['parms'], $sql['msg'], $ouputTable);
}
$ouputTable->render();
$this->output->writeln("");
return $this;
}
}

View File

@@ -12,6 +12,7 @@
namespace Alchemy\Phrasea\Command\Setup; namespace Alchemy\Phrasea\Command\Setup;
use Alchemy\Phrasea\Command\Command; use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Core\Configuration\StructureTemplate;
use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\ArrayInput;
@@ -23,11 +24,18 @@ use Symfony\Component\Process\ExecutableFinder;
class Install extends Command class Install extends Command
{ {
private $executableFinder; private $executableFinder;
/** @var StructureTemplate StructureTemplate */
private $structureTemplate;
public function __construct($name = null) /**
* @param null|string $name
* @param StructureTemplate $structureTemplate
*/
public function __construct($name, $structureTemplate)
{ {
parent::__construct($name); parent::__construct($name);
$this->structureTemplate = $structureTemplate;
$this->executableFinder = new ExecutableFinder(); $this->executableFinder = new ExecutableFinder();
$this $this
@@ -38,9 +46,9 @@ class Install extends Command
->addOption('db-port', null, InputOption::VALUE_OPTIONAL, 'MySQL server port', 3306) ->addOption('db-port', null, InputOption::VALUE_OPTIONAL, 'MySQL server port', 3306)
->addOption('db-user', null, InputOption::VALUE_OPTIONAL, 'MySQL server user', 'phrasea') ->addOption('db-user', null, InputOption::VALUE_OPTIONAL, 'MySQL server user', 'phrasea')
->addOption('db-password', null, InputOption::VALUE_OPTIONAL, 'MySQL server password', null) ->addOption('db-password', null, InputOption::VALUE_OPTIONAL, 'MySQL server password', null)
->addOption('db-template', null, InputOption::VALUE_OPTIONAL, 'Metadata structure language template (available are fr (french) and en (english))', null)
->addOption('databox', null, InputOption::VALUE_OPTIONAL, 'Database name for the DataBox', null)
->addOption('appbox', null, InputOption::VALUE_OPTIONAL, 'Database name for the ApplicationBox', null) ->addOption('appbox', null, InputOption::VALUE_OPTIONAL, 'Database name for the ApplicationBox', null)
->addOption('databox', null, InputOption::VALUE_OPTIONAL, 'Database name for the DataBox', null)
->addOption('db-template', null, InputOption::VALUE_OPTIONAL, 'Databox template (' . $this->structureTemplate->toString() . ')', null)
->addOption('data-path', null, InputOption::VALUE_OPTIONAL, 'Path to data repository', realpath(__DIR__ . '/../../../../../datas')) ->addOption('data-path', null, InputOption::VALUE_OPTIONAL, 'Path to data repository', realpath(__DIR__ . '/../../../../../datas'))
->addOption('server-name', null, InputOption::VALUE_OPTIONAL, 'Server name') ->addOption('server-name', null, InputOption::VALUE_OPTIONAL, 'Server name')
->addOption('indexer', null, InputOption::VALUE_OPTIONAL, 'Path to Phraseanet Indexer', 'auto') ->addOption('indexer', null, InputOption::VALUE_OPTIONAL, 'Path to Phraseanet Indexer', 'auto')
@@ -49,11 +57,22 @@ class Install extends Command
return $this; return $this;
} }
private function serverNameToAppBoxName($serverName)
{
return "ab_" . $serverName;
}
private function serverNameToDataBoxName($serverName)
{
return "db_" . $serverName;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function doExecute(InputInterface $input, OutputInterface $output) protected function doExecute(InputInterface $input, OutputInterface $output)
{ {
/** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog'); $dialog = $this->getHelperSet()->get('dialog');
$output->writeln("<comment> $output->writeln("<comment>
@@ -91,12 +110,16 @@ class Install extends Command
} }
} }
$abConn = $this->getABConn($input, $output, $dialog); $serverName = $this->getServerName($input, $output, $dialog);
list($dbConn, $template) = $this->getDBConn($input, $output, $abConn, $dialog); $abConn = $this->getABConn($input, $output, $dialog, $serverName);
if(!$abConn) {
return 1; // no ab is fatal
}
list($dbConn, $templateName) = $this->getDBConn($input, $output, $abConn, $dialog);
list($email, $password) = $this->getCredentials($input, $output, $dialog); list($email, $password) = $this->getCredentials($input, $output, $dialog);
$dataPath = $this->getDataPath($input, $output, $dialog); $dataPath = $this->getDataPath($input, $output, $dialog);
$serverName = $this->getServerName($input, $output, $dialog);
if (!$input->getOption('yes')) { if (!$input->getOption('yes')) {
$continue = $dialog->askConfirmation($output, "<question>Phraseanet is going to be installed, continue ? (N/y)</question>", false); $continue = $dialog->askConfirmation($output, "<question>Phraseanet is going to be installed, continue ? (N/y)</question>", false);
@@ -108,32 +131,32 @@ class Install extends Command
} }
} }
$this->container['phraseanet.installer']->install($email, $password, $abConn, $serverName, $dataPath, $dbConn, $template, $this->detectBinaries()); $this->container['phraseanet.installer']->install($email, $password, $abConn, $serverName, $dataPath, $dbConn, $templateName, $this->detectBinaries());
if (null !== $this->getApplication()) { if (null !== $this->getApplication()) {
$command = $this->getApplication()->find('crossdomain:generate'); $command = $this->getApplication()->find('crossdomain:generate');
$command->run(new ArrayInput(array( $command->run(new ArrayInput([
'command' => 'crossdomain:generate' 'command' => 'crossdomain:generate'
)), $output); ]), $output);
} }
$output->writeln("<info>Install successful !</info>"); $output->writeln("<info>Install successful !</info>");
return; return 0;
} }
private function getABConn(InputInterface $input, OutputInterface $output, DialogHelper $dialog) private function getABConn(InputInterface $input, OutputInterface $output, DialogHelper $dialog)
{ {
$abConn = $info = null; $abConn = $info = null;
if (!$input->getOption('appbox')) { if (!$input->getOption('appbox')) {
$output->writeln("\n<info>--- Database credentials ---</info>\n"); $output->writeln("<info>--- Database credentials ---</info>");
do { do {
$hostname = $dialog->ask($output, "DB hostname (localhost) : ", 'localhost'); $hostname = $dialog->ask($output, 'DB hostname <comment>[default: "localhost"]</comment> : ', 'localhost');
$port = $dialog->ask($output, "DB port (3306) : ", 3306); $port = $dialog->ask($output, 'DB port <comment>[default: "3306"]</comment> : ', '3306');
$dbUser = $dialog->ask($output, "DB user : "); $dbUser = $dialog->ask($output, 'DB user : ');
$dbPassword = $dialog->askHiddenResponse($output, "DB password (hidden) : "); $dbPassword = $dialog->askHiddenResponse($output, 'DB password (hidden) : ');
$abName = $dialog->ask($output, "DB name (phraseanet) : ", 'phraseanet'); $abName = $dialog->ask($output, 'ApplicationBox name <comment>[default: "phraseanet"]</comment> : ', 'phraseanet');
$info = [ $info = [
'host' => $hostname, 'host' => $hostname,
@@ -145,9 +168,10 @@ class Install extends Command
try { try {
$abConn = $this->container['dbal.provider']($info); $abConn = $this->container['dbal.provider']($info);
$abConn->connect(); $abConn->connect();
$output->writeln("\n\t<info>Application-Box : Connection successful !</info>\n"); $output->writeln("<info>Application-Box : Connection successful !</info>");
} catch (\Exception $e) { } catch (\Exception $e) {
$output->writeln("\n\t<error>Invalid connection parameters</error>\n"); $output->writeln("<error>Application-Box : Failed to connect, try again.</error>");
$abConn = null;
} }
} while (!$abConn); } while (!$abConn);
} else { } else {
@@ -161,7 +185,7 @@ class Install extends Command
$abConn = $this->container['dbal.provider']($info); $abConn = $this->container['dbal.provider']($info);
$abConn->connect(); $abConn->connect();
$output->writeln("\n\t<info>Application-Box : Connection successful !</info>\n"); $output->writeln("<info>Application-Box : Connection successful !</info>");
} }
// add dbs.option & orm.options services to use orm.em later // add dbs.option & orm.options services to use orm.em later
@@ -175,12 +199,13 @@ class Install extends Command
private function getDBConn(InputInterface $input, OutputInterface $output, Connection $abConn, DialogHelper $dialog) private function getDBConn(InputInterface $input, OutputInterface $output, Connection $abConn, DialogHelper $dialog)
{ {
$dbConn = $template = $info = null; $dbConn = $info = null;
$templates = $this->container['phraseanet.structure-template']->getAvailable(); $templateName = null;
if (!$input->getOption('databox')) { if (!$input->getOption('databox')) {
do { do {
$retry = false; $retry = false;
$dbName = $dialog->ask($output, 'DataBox name, will not be created if empty : ', null); $dbName = $dialog->ask($output, 'Data-Box name, will not be created if empty : ', null);
if ($dbName) { if ($dbName) {
try { try {
@@ -194,19 +219,13 @@ class Install extends Command
$dbConn = $this->container['dbal.provider']($info); $dbConn = $this->container['dbal.provider']($info);
$dbConn->connect(); $dbConn->connect();
$output->writeln("\n\t<info>Data-Box : Connection successful !</info>\n"); $output->writeln("<info>Data-Box : Connection successful !</info>");
do {
$template = $dialog->ask($output, "Choose a language template for metadata structure, available are {$templates->__toString()} : ", 'en');
}
while (!in_array($template, array_keys($templates->getTemplates())));
$output->writeln("\n\tLanguage selected is <info>'$template'</info>\n");
} catch (\Exception $e) { } catch (\Exception $e) {
$output->writeln(" <error>Data-Box : Failed to connect, try again.</error>");
$retry = true; $retry = true;
} }
} else { } else {
$output->writeln("\n\tNo databox will be created\n"); $output->writeln("No databox will be created");
} }
} while ($retry); } while ($retry);
} else { } else {
@@ -220,17 +239,37 @@ class Install extends Command
$dbConn = $this->container['dbal.provider']($info); $dbConn = $this->container['dbal.provider']($info);
$dbConn->connect(); $dbConn->connect();
$output->writeln("\n\t<info>Data-Box : Connection successful !</info>\n"); $output->writeln("<info>Data-Box : Connection successful !</info>");
$template = $input->getOption('db-template') ? : 'en';
} }
// add dbs.option & orm.options services to use orm.em later // add dbs.option & orm.options services to use orm.em later
if ($dbConn && $info) { if ($dbConn && $info) {
/** @var StructureTemplate $templates */
$templates = $this->container['phraseanet.structure-template'];
// if a template name is provided, check that this template exists
$templateName = $input->getOption('db-template');
if($templateName && !$templates->getByName($templateName)) {
throw new \Exception_InvalidArgument(sprintf("Databox template \"%s\" not found.", $templateName));
}
if(!$templateName) {
// propose a default template : the first available if "en-simple" does not exists.
$defaultDBoxTemplate = $this->structureTemplate->getDefault();
do {
$templateName = $dialog->ask($output, 'Choose a template from ('.$templates->toString().') for metadata structure <comment>[default: "'.$defaultDBoxTemplate.'"]</comment> : ', $defaultDBoxTemplate);
if(!$templates->getByName($templateName)) {
$output->writeln("<error>Data-Box template : Template not found, try again.</error>");
}
}
while (!$templates->getByName($templateName));
}
$this->container['dbs.options'] = array_merge($this->container['db.options.from_info']($info), $this->container['dbs.options']); $this->container['dbs.options'] = array_merge($this->container['db.options.from_info']($info), $this->container['dbs.options']);
$this->container['orm.ems.options'] = array_merge($this->container['orm.em.options.from_info']($info), $this->container['orm.ems.options']); $this->container['orm.ems.options'] = array_merge($this->container['orm.em.options.from_info']($info), $this->container['orm.ems.options']);
} }
return [$dbConn, $template]; return [$dbConn, $templateName];
} }
private function getCredentials(InputInterface $input, OutputInterface $output, DialogHelper $dialog) private function getCredentials(InputInterface $input, OutputInterface $output, DialogHelper $dialog)
@@ -238,7 +277,7 @@ class Install extends Command
$email = $password = null; $email = $password = null;
if (!$input->getOption('email') && !$input->getOption('password')) { if (!$input->getOption('email') && !$input->getOption('password')) {
$output->writeln("\n<info>--- Account Informations ---</info>\n"); $output->writeln("<info>--- Account Informations ---</info>");
do { do {
$email = $dialog->ask($output, 'Please provide a valid e-mail address : '); $email = $dialog->ask($output, 'Please provide a valid e-mail address : ');
@@ -248,7 +287,7 @@ class Install extends Command
$password = $dialog->askHiddenResponse($output, 'Please provide a password (hidden, 6 character min) : '); $password = $dialog->askHiddenResponse($output, 'Please provide a password (hidden, 6 character min) : ');
} while (strlen($password) < 6); } while (strlen($password) < 6);
$output->writeln("\n\t<info>Email / Password successfully set</info>\n"); $output->writeln("<info>Email / Password successfully set</info>");
} elseif ($input->getOption('email') && $input->getOption('password')) { } elseif ($input->getOption('email') && $input->getOption('password')) {
if (!\Swift_Validate::email($input->getOption('email'))) { if (!\Swift_Validate::email($input->getOption('email'))) {
throw new \RuntimeException('Invalid email addess'); throw new \RuntimeException('Invalid email addess');

View File

@@ -37,7 +37,7 @@ class XSendFileMappingGenerator extends Command
protected function doExecute(InputInterface $input, OutputInterface $output) protected function doExecute(InputInterface $input, OutputInterface $output)
{ {
$extractor = new DataboxPathExtractor($this->container->getApplicationBox()); $extractor = new DataboxPathExtractor($this->container->getApplicationBox());
$paths = $extractor->extractPaths(); $paths = $extractor->extractPaths('xsendfile');
foreach ($paths as $path) { foreach ($paths as $path) {
$this->container['filesystem']->mkdir($path); $this->container['filesystem']->mkdir($path);
} }

View File

@@ -34,11 +34,14 @@ abstract class AbstractDelivery
{ {
$mediaSubdefinition = $record->get_subdef($subdef); $mediaSubdefinition = $record->get_subdef($subdef);
$filename = $request->get("filename") ?: $mediaSubdefinition->get_file();
$pathOut = $this->tamperProofSubDefinition($mediaSubdefinition, $watermark, $stamp); $pathOut = $this->tamperProofSubDefinition($mediaSubdefinition, $watermark, $stamp);
$disposition = $request->query->get('download') ? DeliverDataInterface::DISPOSITION_ATTACHMENT : DeliverDataInterface::DISPOSITION_INLINE; $disposition = $request->query->get('download') ? DeliverDataInterface::DISPOSITION_ATTACHMENT : DeliverDataInterface::DISPOSITION_INLINE;
$response = $this->deliverFile($pathOut, $mediaSubdefinition->get_file(), $disposition, $mediaSubdefinition->get_mime()); // nb: $filename will be sanitized, no need to do it here
$response = $this->deliverFile($pathOut, $filename, $disposition, $mediaSubdefinition->get_mime());
if (in_array($subdef, array('document', 'preview'))) { if (in_array($subdef, array('document', 'preview'))) {
$response->setPrivate(); $response->setPrivate();

View File

@@ -246,8 +246,8 @@ class RootController extends Controller
'searchable' => $request->request->get('searchable') ? '1' : '0', 'searchable' => $request->request->get('searchable') ? '1' : '0',
'printable' => $request->request->get('printable') ? '1' : '0', 'printable' => $request->request->get('printable') ? '1' : '0',
'name' => $request->request->get('name', ''), 'name' => $request->request->get('name', ''),
'labelon' => $request->request->get('label_on', ''), 'labelon' => htmlentities($request->request->get('label_on', '')),
'labeloff' => $request->request->get('label_off', ''), 'labeloff' => htmlentities($request->request->get('label_off', '')),
'labels_on' => $request->request->get('labels_on', []), 'labels_on' => $request->request->get('labels_on', []),
'labels_off' => $request->request->get('labels_off', []), 'labels_off' => $request->request->get('labels_off', []),
]; ];

View File

@@ -208,10 +208,10 @@ class SubdefsController extends Controller
protected function getSubviewsMapping() protected function getSubviewsMapping()
{ {
$mapping = [ $mapping = [
Type::TYPE_IMAGE => [Subdef::TYPE_IMAGE], Type::TYPE_IMAGE => [Subdef::TYPE_IMAGE, Subdef::TYPE_PDF],
Type::TYPE_VIDEO => [Subdef::TYPE_IMAGE, Subdef::TYPE_VIDEO, Subdef::TYPE_ANIMATION], Type::TYPE_VIDEO => [Subdef::TYPE_IMAGE, Subdef::TYPE_VIDEO, Subdef::TYPE_ANIMATION],
Type::TYPE_AUDIO => [Subdef::TYPE_IMAGE, Subdef::TYPE_AUDIO], Type::TYPE_AUDIO => [Subdef::TYPE_IMAGE, Subdef::TYPE_AUDIO],
Type::TYPE_DOCUMENT => [Subdef::TYPE_IMAGE, Subdef::TYPE_FLEXPAPER], Type::TYPE_DOCUMENT => [Subdef::TYPE_IMAGE, Subdef::TYPE_FLEXPAPER, Subdef::TYPE_PDF],
Type::TYPE_FLASH => [Subdef::TYPE_IMAGE] Type::TYPE_FLASH => [Subdef::TYPE_IMAGE]
]; ];
@@ -229,7 +229,7 @@ class SubdefsController extends Controller
"JPG" => null, "JPG" => null,
"160px JPG" => [ "160px JPG" => [
Image::OPTION_SIZE => "160", Image::OPTION_SIZE => "160",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -238,7 +238,7 @@ class SubdefsController extends Controller
], ],
"320 px JPG (thumbnail Phraseanet)" => [ "320 px JPG (thumbnail Phraseanet)" => [
Image::OPTION_SIZE => "320", Image::OPTION_SIZE => "320",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -247,7 +247,7 @@ class SubdefsController extends Controller
], ],
"640px JPG" => [ "640px JPG" => [
Image::OPTION_SIZE => "640", Image::OPTION_SIZE => "640",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -256,7 +256,7 @@ class SubdefsController extends Controller
], ],
"1280px JPG (preview Phraseanet)" => [ "1280px JPG (preview Phraseanet)" => [
Image::OPTION_SIZE => "1280", Image::OPTION_SIZE => "1280",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -265,7 +265,7 @@ class SubdefsController extends Controller
], ],
"2560px JPG" => [ "2560px JPG" => [
Image::OPTION_SIZE => "2560", Image::OPTION_SIZE => "2560",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -275,7 +275,7 @@ class SubdefsController extends Controller
"PNG" => null, "PNG" => null,
"160px PNG 8 bits" => [ "160px PNG 8 bits" => [
Image::OPTION_SIZE => "160", Image::OPTION_SIZE => "160",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -284,7 +284,7 @@ class SubdefsController extends Controller
], ],
"320px PNG 8 bits" => [ "320px PNG 8 bits" => [
Image::OPTION_SIZE => "320", Image::OPTION_SIZE => "320",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -293,7 +293,7 @@ class SubdefsController extends Controller
], ],
"640px PNG 8 bits" => [ "640px PNG 8 bits" => [
Image::OPTION_SIZE => "640", Image::OPTION_SIZE => "640",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -302,7 +302,7 @@ class SubdefsController extends Controller
], ],
"1280px PNG 8 bits" => [ "1280px PNG 8 bits" => [
Image::OPTION_SIZE => "1280", Image::OPTION_SIZE => "1280",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -311,7 +311,7 @@ class SubdefsController extends Controller
], ],
"2560px PNG 8 bits" => [ "2560px PNG 8 bits" => [
Image::OPTION_SIZE => "2560", Image::OPTION_SIZE => "2560",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -321,7 +321,7 @@ class SubdefsController extends Controller
"TIFF" => null, "TIFF" => null,
"1280 TIFF" => [ "1280 TIFF" => [
Image::OPTION_SIZE => "1280", Image::OPTION_SIZE => "1280",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -330,7 +330,7 @@ class SubdefsController extends Controller
], ],
"2560px TIFF" => [ "2560px TIFF" => [
Image::OPTION_SIZE => "2560", Image::OPTION_SIZE => "2560",
Image::OPTION_RESOLUTION => "75", Image::OPTION_RESOLUTION => "72",
Image::OPTION_STRIP => "yes", Image::OPTION_STRIP => "yes",
Image::OPTION_FLATTEN => "yes", Image::OPTION_FLATTEN => "yes",
Image::OPTION_QUALITY => "75", Image::OPTION_QUALITY => "75",
@@ -554,6 +554,11 @@ class SubdefsController extends Controller
], ],
"form" => [], "form" => [],
], ],
Subdef::TYPE_PDF => array(
"definitions" => array(
),
"form" => array(),
),
]; ];
return $config; return $config;

View File

@@ -86,10 +86,6 @@ class TaskManagerController extends Controller
public function getLiveInformation(Request $request) public function getLiveInformation(Request $request)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
if ($request->getRequestFormat() !== "json") { if ($request->getRequestFormat() !== "json") {
$this->app->abort(406, 'Only JSON format is accepted.'); $this->app->abort(406, 'Only JSON format is accepted.');
} }
@@ -108,23 +104,28 @@ class TaskManagerController extends Controller
public function getScheduler(Request $request) public function getScheduler(Request $request)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
if ($request->getRequestFormat() !== "json") { if ($request->getRequestFormat() !== "json") {
$this->app->abort(406, 'Only JSON format is accepted.'); $this->app->abort(406, 'Only JSON format is accepted.');
} }
$ret = [
return $this->app->json([
'name' => $this->app->trans('Task Scheduler'), 'name' => $this->app->trans('Task Scheduler'),
'configuration' => $this->app['task-manager.status']->getStatus(), 'configuration' => $this->app['task-manager.status']->getStatus(),
'urls' => [ ];
if (($this->app['phraseanet.configuration']['main']['task-manager']['enabled'] === true)) {
$ret['urls'] = [
'start' => $this->app->path('admin_tasks_scheduler_start'), 'start' => $this->app->path('admin_tasks_scheduler_start'),
'stop' => $this->app->path('admin_tasks_scheduler_stop'), 'stop' => $this->app->path('admin_tasks_scheduler_stop'),
'log' => $this->app->path('admin_tasks_scheduler_log'),
];
}
else {
$ret['urls'] = [
'log' => $this->app->path('admin_tasks_scheduler_log'), 'log' => $this->app->path('admin_tasks_scheduler_log'),
] ];
]); }
return $this->app->json($ret);
} }
public function getTasks(Request $request) public function getTasks(Request $request)
@@ -212,10 +213,6 @@ class TaskManagerController extends Controller
public function postTaskDelete(Task $task) public function postTaskDelete(Task $task)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
$this->getTaskManipulator()->delete($task); $this->getTaskManipulator()->delete($task);
return $this->app->redirectPath('admin_tasks_list'); return $this->app->redirectPath('admin_tasks_list');
@@ -223,10 +220,6 @@ class TaskManagerController extends Controller
public function postStartTask(Task $task) public function postStartTask(Task $task)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
$this->getTaskManipulator()->start($task); $this->getTaskManipulator()->start($task);
return $this->app->redirectPath('admin_tasks_list'); return $this->app->redirectPath('admin_tasks_list');
@@ -234,10 +227,6 @@ class TaskManagerController extends Controller
public function postStopTask(Task $task) public function postStopTask(Task $task)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
$this->getTaskManipulator()->stop($task); $this->getTaskManipulator()->stop($task);
return $this->app->redirectPath('admin_tasks_list'); return $this->app->redirectPath('admin_tasks_list');
@@ -252,10 +241,6 @@ class TaskManagerController extends Controller
public function postSaveTask(Request $request, Task $task) public function postSaveTask(Request $request, Task $task)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
if (!$this->doValidateXML($request->request->get('settings'))) { if (!$this->doValidateXML($request->request->get('settings'))) {
return $this->app->json(['success' => false, 'message' => sprintf('Unable to load XML %s', $request->request->get('xml'))]); return $this->app->json(['success' => false, 'message' => sprintf('Unable to load XML %s', $request->request->get('xml'))]);
} }
@@ -292,10 +277,6 @@ class TaskManagerController extends Controller
public function getTask(Request $request, Task $task) public function getTask(Request $request, Task $task)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
if ('json' === $request->getContentType()) { if ('json' === $request->getContentType()) {
return $this->app->json(array_replace([ return $this->app->json(array_replace([
'id' => $task->getId(), 'id' => $task->getId(),
@@ -322,10 +303,6 @@ class TaskManagerController extends Controller
public function validateXML(Request $request) public function validateXML(Request $request)
{ {
if (false === $this->app['phraseanet.configuration']['main']['task-manager']['enabled']) {
throw new RuntimeException('The use of the task manager is disabled on this instance.');
}
return $this->app->json(['success' => $this->doValidateXML($request->getContent())]); return $this->app->json(['success' => $this->doValidateXML($request->getContent())]);
} }

View File

@@ -167,6 +167,103 @@ class OAuth2Controller extends Controller
return ''; return '';
} }
public function authorizeWithProviderAction(Request $request, $providerId)
{
$context = new Context(Context::CONTEXT_OAUTH2_NATIVE);
$this->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context));
//Check for auth params, send error or redirect if not valid
$params = $this->oAuth2Adapter->getAuthorizationRequestParameters($request);
/** @var ApiApplicationRepository $appRepository */
$appRepository = $this->app['repo.api-applications'];
if (null === $client = $appRepository->findByClientId($params['client_id'])) {
throw new NotFoundHttpException(sprintf('Application with client id %s could not be found', $params['client_id']));
}
$provider = $this->findProvider($providerId);
return $provider->authenticate($request->query->all());
}
public function authorizeCallbackAction(Request $request, $providerId)
{
$context = new Context(Context::CONTEXT_OAUTH2_NATIVE);
$provider = $this->findProvider($providerId);
$params = $this->oAuth2Adapter->getAuthorizationRequestParameters($request);
// triggers what's necessary
try {
$provider->onCallback($request);
$token = $provider->getToken();
} catch (NotAuthenticatedException $e) {
$this->getSession()->getFlashBag()->add('error', $this->app->trans('Unable to authenticate with %provider_name%', ['%provider_name%' => $provider->getName()]));
return $this->app->redirectPath('oauth2_authorize', array_merge(array('error' => 'login'), $params));
}
$userAuthProvider = $this->getUserAuthProviderRepository()
->findWithProviderAndId($token->getProvider()->getId(), $token->getId());
if($userAuthProvider == null){
unset($params['state']);
return $this->app->redirectPath('oauth2_authorize', array_merge(array('error' => 'login'), $params));
}
try {
$user = $this->getAuthenticationSuggestionFinder()->find($token);
} catch (NotAuthenticatedException $e) {
$this->app->addFlash('error', $this->app->trans('Unable to retrieve provider identity'));
return $this->app->redirectPath('oauth2_authorize', array_merge(array('error' => 'login'), $params));
}
$this->getAuthenticator()->openAccount($userAuthProvider->getUser());
$event = new PostAuthenticate($request, new Response(), $user, $context);
$this->dispatch(PhraseaEvents::POST_AUTHENTICATE, $event);
/** @var ApiApplicationRepository $appRepository */
$appRepository = $this->app['repo.api-applications'];
if (null === $client = $appRepository->findByClientId($params['client_id'])) {
throw new NotFoundHttpException(sprintf('Application with client id %s could not be found', $params['client_id']));
}
$this->oAuth2Adapter->setClient($client);
//check if current client is already authorized by current user
$clients = $appRepository->findAuthorizedAppsByUser($this->getAuthenticatedUser());
$appAuthorized = false;
foreach ($clients as $authClient) {
if ($client->getClientId() == $authClient->getClientId()) {
$appAuthorized = true;
break;
}
}
$account = $this->oAuth2Adapter->updateAccount($this->getAuthenticatedUser());
$params['account_id'] = $account->getId();
//if native app show template
if ($this->oAuth2Adapter->isNativeApp($params['redirect_uri'])) {
$params = $this->oAuth2Adapter->finishNativeClientAuthorization($appAuthorized, $params);
$r = new Response($this->render("api/auth/native_app_access_token.html.twig", $params));
$r->headers->set('Content-Type', 'text/html');
return $r;
}
$this->oAuth2Adapter->finishClientAuthorization($appAuthorized, $params);
// As OAuth2 library already outputs response content, we need to send an empty
// response to avoid breaking silex controller
return '';
}
/** /**
* TOKEN ENDPOINT * TOKEN ENDPOINT
* Token endpoint - used to exchange an authorization grant for an access token. * Token endpoint - used to exchange an authorization grant for an access token.
@@ -206,4 +303,41 @@ class OAuth2Controller extends Controller
{ {
return $this->app['manipulator.api-account']; return $this->app['manipulator.api-account'];
} }
/**
* @param string $providerId
* @return ProviderInterface
*/
private function findProvider($providerId)
{
try {
return $this->getAuthenticationProviders()->get($providerId);
} catch (InvalidArgumentException $e) {
throw new NotFoundHttpException('The requested provider does not exist');
}
}
/**
* @return ProvidersCollection
*/
private function getAuthenticationProviders()
{
return $this->app['authentication.providers'];
}
/**
* @return UsrAuthProviderRepository
*/
private function getUserAuthProviderRepository()
{
return $this->app['repo.usr-auth-providers'];
}
/**
* @return SuggestionFinder
*/
private function getAuthenticationSuggestionFinder()
{
return $this->app['authentication.suggestion-finder'];
}
} }

View File

@@ -59,14 +59,14 @@ class SearchController extends Controller
$result = $this->getSearchEngine()->query($query, $options); $result = $this->getSearchEngine()->query($query, $options);
$this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $result->getUserQuery()); $this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $result->getQueryText());
// log array of collectionIds (from $options) for each databox // log array of collectionIds (from $options) for each databox
$collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox(); $collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox();
foreach ($collectionsReferencesByDatabox as $sbid => $references) { foreach ($collectionsReferencesByDatabox as $sbid => $references) {
$databox = $this->findDataboxById($sbid); $databox = $this->findDataboxById($sbid);
$collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references); $collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references);
$this->getSearchEngineLogger()->log($databox, $result->getUserQuery(), $result->getTotal(), $collectionsIds); $this->getSearchEngineLogger()->log($databox, $result->getQueryText(), $result->getTotal(), $collectionsIds);
} }
$this->getSearchEngine()->clearCache(); $this->getSearchEngine()->clearCache();

View File

@@ -17,6 +17,7 @@ use Alchemy\Phrasea\Account\Command\UpdatePasswordCommand;
use Alchemy\Phrasea\Account\RestrictedStatusExtractor; use Alchemy\Phrasea\Account\RestrictedStatusExtractor;
use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware; use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware;
use Alchemy\Phrasea\Application\Helper\DispatcherAware; use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\FilesystemAware;
use Alchemy\Phrasea\Application\Helper\JsonBodyAware; use Alchemy\Phrasea\Application\Helper\JsonBodyAware;
use Alchemy\Phrasea\Authentication\Exception\RegistrationException; use Alchemy\Phrasea\Authentication\Exception\RegistrationException;
use Alchemy\Phrasea\Authentication\RegistrationService; use Alchemy\Phrasea\Authentication\RegistrationService;
@@ -78,6 +79,7 @@ use Alchemy\Phrasea\Search\TechnicalDataView;
use Alchemy\Phrasea\Search\V1SearchCompositeResultTransformer; use Alchemy\Phrasea\Search\V1SearchCompositeResultTransformer;
use Alchemy\Phrasea\Search\V1SearchRecordsResultTransformer; use Alchemy\Phrasea\Search\V1SearchRecordsResultTransformer;
use Alchemy\Phrasea\Search\V1SearchResultTransformer; use Alchemy\Phrasea\Search\V1SearchResultTransformer;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface; use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
use Alchemy\Phrasea\SearchEngine\SearchEngineLogger; use Alchemy\Phrasea\SearchEngine\SearchEngineLogger;
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions; use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
@@ -88,6 +90,7 @@ use Alchemy\Phrasea\Utilities\NullableDateTime;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
use JMS\TranslationBundle\Annotation\Ignore; use JMS\TranslationBundle\Annotation\Ignore;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
use media_subdef;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -96,10 +99,12 @@ use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
class V1Controller extends Controller class V1Controller extends Controller
{ {
use DataboxLoggerAware; use DataboxLoggerAware;
use DispatcherAware; use DispatcherAware;
use FilesystemAware;
use JsonBodyAware; use JsonBodyAware;
const OBJECT_TYPE_USER = 'http://api.phraseanet.com/api/objects/user'; const OBJECT_TYPE_USER = 'http://api.phraseanet.com/api/objects/user';
@@ -685,8 +690,18 @@ class V1Controller extends Controller
$checks = array_map(function (LazaretCheck $checker) use ($manager, $translator) { $checks = array_map(function (LazaretCheck $checker) use ($manager, $translator) {
$checkerFQCN = $checker->getCheckClassname(); $checkerFQCN = $checker->getCheckClassname();
return $manager->getCheckerFromFQCN($checkerFQCN)->getMessage($translator); return $manager->getCheckerFromFQCN($checkerFQCN)->getMessage($translator);
}, iterator_to_array($file->getChecks())); }, $file->getChecksWhithNameKey());
$recordsMatch = array_map(function ($recordsTab){
$record = $recordsTab['record'];
$matched['record_id'] = $record->getRecordId();
$matched['collection'] = $record->getCollectionName();
$matched['checks'] = $recordsTab['reasons'];
return $matched;
}, array_values($file->getRecordsToSubstitute($this->app, true)));
$usr_id = $user = null; $usr_id = $user = null;
if ($file->getSession()->getUser()) { if ($file->getSession()->getUser()) {
@@ -705,10 +720,12 @@ class V1Controller extends Controller
'quarantine_session' => $session, 'quarantine_session' => $session,
'base_id' => $file->getBaseId(), 'base_id' => $file->getBaseId(),
'original_name' => $file->getOriginalName(), 'original_name' => $file->getOriginalName(),
'collection' => $file->getCollection($this->app)->get_label($this->app['locale']),
'sha256' => $file->getSha256(), 'sha256' => $file->getSha256(),
'uuid' => $file->getUuid(), 'uuid' => $file->getUuid(),
'forced' => $file->getForced(), 'forced' => $file->getForced(),
'checks' => $file->getForced() ? [] : $checks, 'checks' => $file->getForced() ? [] : $checks,
'records_match' => $recordsMatch?:[],
'created_on' => $file->getCreated()->format(DATE_ATOM), 'created_on' => $file->getCreated()->format(DATE_ATOM),
'updated_on' => $file->getUpdated()->format(DATE_ATOM), 'updated_on' => $file->getUpdated()->format(DATE_ATOM),
]; ];
@@ -913,7 +930,14 @@ class V1Controller extends Controller
))->createResponse(); ))->createResponse();
} }
$media = $this->app->getMediaFromUri($file->getPathname()); // Add file extension
$uploadedFilename = $file->getRealPath();
$renamedFilename = $file->getRealPath() . '.' . pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION);
$this->getFilesystem()->rename($uploadedFilename, $renamedFilename);
$media = $this->app->getMediaFromUri($renamedFilename);
$Package = new File($this->app, $media, $collection, $file->getClientOriginalName()); $Package = new File($this->app, $media, $collection, $file->getClientOriginalName());
@@ -1000,7 +1024,15 @@ class V1Controller extends Controller
return $this->getBadRequestAction($request, 'Missing name parameter'); return $this->getBadRequestAction($request, 'Missing name parameter');
} }
$media = $this->app->getMediaFromUri($file->getPathname()); // Add file extension
$uploadedFilename = $file->getRealPath();
$renamedFilename = $file->getRealPath() . '.' . pathinfo($file->getClientOriginalName(), PATHINFO_EXTENSION);
$this->getFilesystem()->rename($uploadedFilename, $renamedFilename);
$media = $this->app->getMediaFromUri($renamedFilename);
$record = $this->findDataboxById($request->get('databox_id'))->get_record($request->get('record_id')); $record = $this->findDataboxById($request->get('databox_id'))->get_record($request->get('record_id'));
$base_id = $record->getBaseId(); $base_id = $record->getBaseId();
$collection = \collection::getByBaseId($this->app, $base_id); $collection = \collection::getByBaseId($this->app, $base_id);
@@ -1011,7 +1043,11 @@ class V1Controller extends Controller
} }
$adapt = ($request->get('adapt')===null || !(\p4field::isno($request->get('adapt')))); $adapt = ($request->get('adapt')===null || !(\p4field::isno($request->get('adapt'))));
$ret['adapt'] = $adapt; $ret['adapt'] = $adapt;
$this->getSubdefSubstituer()->substitute($record, $request->get('name'), $media, $adapt); if($request->get('name') == 'document') {
$this->getSubdefSubstituer()->substituteDocument($record, $media, $adapt);
} else {
$this->getSubdefSubstituer()->substituteSubdef($record, $request->get('name'), $media, $adapt);
}
foreach ($record->get_embedable_medias() as $name => $media) { foreach ($record->get_embedable_medias() as $name => $media) {
if ($name == $request->get('name') && if ($name == $request->get('name') &&
null !== ($subdef = $this->listEmbeddableMedia($request, $record, $media))) { null !== ($subdef = $this->listEmbeddableMedia($request, $record, $media))) {
@@ -1499,14 +1535,14 @@ class V1Controller extends Controller
$search_result = $this->getSearchEngine()->query((string)$request->get('query'), $options); $search_result = $this->getSearchEngine()->query((string)$request->get('query'), $options);
$this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $search_result->getUserQuery()); $this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $search_result->getQueryText());
// log array of collectionIds (from $options) for each databox // log array of collectionIds (from $options) for each databox
$collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox(); $collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox();
foreach ($collectionsReferencesByDatabox as $sbid => $references) { foreach ($collectionsReferencesByDatabox as $sbid => $references) {
$databox = $this->findDataboxById($sbid); $databox = $this->findDataboxById($sbid);
$collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references); $collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references);
$this->getSearchEngineLogger()->log($databox, $search_result->getUserQuery(), $search_result->getTotal(), $collectionsIds); $this->getSearchEngineLogger()->log($databox, $search_result->getQueryText(), $search_result->getTotal(), $collectionsIds);
} }
$this->getSearchEngine()->clearCache(); $this->getSearchEngine()->clearCache();
@@ -2613,7 +2649,7 @@ class V1Controller extends Controller
continue; continue;
} }
$media = $this->app->getMediaFromUri($value->getRealPath()); $media = $this->app->getMediaFromUri($value->getRealPath());
$this->getSubdefSubstituer()->substitute($story, $name, $media); $this->getSubdefSubstituer()->substituteSubdef($story, $name, $media); // name = thumbnail | preview
$this->getDataboxLogger($story->getDatabox())->log( $this->getDataboxLogger($story->getDatabox())->log(
$story, $story,
\Session_Logger::EVENT_SUBSTITUTE, \Session_Logger::EVENT_SUBSTITUTE,
@@ -2644,6 +2680,205 @@ class V1Controller extends Controller
return Result::create($request, $ret)->createResponse(); return Result::create($request, $ret)->createResponse();
} }
/**
* Returns all documentary fields available for user
* @param Request $request
* @return Response
*/
public function getCurrentUserStructureAction(Request $request)
{
$ret = [
"meta_fields" => $this->listUserAuthorizedMetadataFields($this->getAuthenticatedUser()),
"aggregable_fields" => $this->buildUserFieldList(ElasticsearchOptions::getAggregableTechnicalFields(), ['choices']),
"technical_fields" => $this->buildUserFieldList(media_subdef::getTechnicalFieldsList()),
];
return Result::create($request, $ret)->createResponse();
}
/**
* Returns all sub-definitions available for the user
* @param Request $request
* @return Response
*/
public function getCurrentUserSubdefsAction(Request $request)
{
$ret = [
"subdefs" => $this->listUserAuthorizedSubdefs($this->getAuthenticatedUser()),
];
return Result::create($request, $ret)->createResponse();
}
/**
* Returns all collections available for the user
* @param Request $request
* @return Response
*/
public function getCurrentUserCollectionsAction(Request $request)
{
$ret = [
"collections" => $this->listUserAuthorizedCollections($this->getAuthenticatedUser()),
];
return Result::create($request, $ret)->createResponse();
}
/**
* Returns list of Metadata Fields from the databoxes on which the user has rights
* @param User $user
* @return array
*/
private function listUserAuthorizedMetadataFields(User $user)
{
$acl = $this->getAclForUser($user);
$ret = [];
foreach ($acl->get_granted_sbas() as $databox) {
$databoxId = $databox->get_sbas_id();
foreach ($databox->get_meta_structure() as $databox_field) {
$data = [
'name' => $databox_field->get_name(),
'id' => $databox_field->get_id(),
'databox_id' => $databoxId,
'multivalue' => $databox_field->is_multi(),
'indexable' => $databox_field->is_indexable(),
'readonly' => $databox_field->is_readonly(),
'business' => $databox_field->isBusiness(),
'source' => $databox_field->get_tag()->getTagname(),
'labels' => [
'fr' => $databox_field->get_label('fr'),
'en' => $databox_field->get_label('en'),
'de' => $databox_field->get_label('de'),
'nl' => $databox_field->get_label('nl'),
],
];
$ret[] = $data;
}
if ($acl->can_see_business_fields($databox) === false) {
$ret = array_values($this->removeBusinessFields($ret));
}
}
return $ret;
}
/**
* Build the aggregable/technical fields array
* @param array $fields
* @param array $excludes
* @return array
*/
private function buildUserFieldList(array $fields, array $excludes = [])
{
$ret = [];
foreach ($fields as $key => $field) {
$data['name'] = $key;
foreach ($field as $k => $i) {
if (in_array($k, $excludes)) {
continue;
}
$data[$k] = $i;
}
$ret[] = $data;
}
return $ret;
}
/**
* Returns list of sub-definitions from the databoxes on which the user has rights
* @param User $user
* @return array
*/
private function listUserAuthorizedSubdefs(User $user)
{
$acl = $this->getAclForUser($user);
$ret = [];
foreach ($acl->get_granted_sbas() as $databox) {
$databoxId = $databox->get_sbas_id();
$subdefs = $databox->get_subdef_structure();
foreach ($subdefs as $subGroup) {
foreach ($subGroup->getIterator() as $sub) {
$opt = [];
$data = [
'name' => $sub->get_name(),
'databox_id' => $databoxId,
'class' => $sub->get_class(),
'preset' => $sub->get_preset(),
'downloadable' => $sub->isDownloadable(),
'devices' => $sub->getDevices(),
'labels' => [
'fr' => $sub->get_label('fr'),
'en' => $sub->get_label('en'),
'de' => $sub->get_label('de'),
'nl' => $sub->get_label('nl'),
],
];
$options = $sub->getOptions();
foreach ($options as $option) {
$opt[$option->getName()] = $option->getValue();
}
$data['options'] = $opt;
$ret[$subGroup->getName()][$sub->get_name()] = $data;
}
}
}
return $ret;
}
/**
* Returns list of collection from the databoxes on which the user has rights
* @param User $user
* @return array
*/
private function listUserAuthorizedCollections(User $user)
{
$acl = $this->getAclForUser($user);
$rights = $acl->get_bas_rights();
$bases = $acl->get_granted_base();
$grants = [];
$statusMapper = new RestrictedStatusExtractor($acl, $this->getApplicationBox());
foreach ($bases as $base) {
$baseGrants = [];
foreach ($rights as $right) {
if (!$acl->has_right_on_base($base->get_base_id(), $right)) {
continue;
}
$baseGrants[] = $right;
}
$grants[] = [
'databox_id' => $base->get_sbas_id(),
'base_id' => $base->get_base_id(),
'collection_id' => $base->get_coll_id(),
'name' => $base->get_name(),
'logo' => $base->get_binary_minilogos() ? base64_encode($base->get_binary_minilogos()) : '',
'labels' => [
'fr' => $base->get_label('fr'),
'en' => $base->get_label('en'),
'de' => $base->get_label('de'),
'nl' => $base->get_label('nl'),
],
'rights' => $baseGrants,
'statuses' => $statusMapper->getRestrictedStatuses($base->get_base_id())
];
}
return $grants;
}
public function deleteCurrentUserAction(Request $request) public function deleteCurrentUserAction(Request $request)
{ {
try { try {
@@ -3093,4 +3328,16 @@ class V1Controller extends Controller
$recordView->setCaption($captionView); $recordView->setCaption($captionView);
} }
} }
/**
* Remove business metadata fields
* @param array $fields
* @return array
*/
private function removeBusinessFields(array $fields)
{
return array_filter($fields, function ($field) {
return $field['business'] !== true;
});
}
} }

View File

@@ -78,17 +78,39 @@ class LightboxController extends Controller
{ {
/** @var BasketElementRepository $repository */ /** @var BasketElementRepository $repository */
$repository = $this->app['repo.basket-elements']; $repository = $this->app['repo.basket-elements'];
$basketElement = $repository->findUserElement($sselcont_id, $this->getAuthenticatedUser()); $basketElement = $repository->findUserElement($sselcont_id, $this->getAuthenticatedUser());
$basket = $basketElement->getBasket();
$elements = $basket->getElements();
for ($i = 0; $i < count($elements); ++$i) {
if ($sselcont_id == $elements[$i]->getId()) {
$nextKey = $i + 1;
$prevKey = $i - 1;
if ($nextKey < count($elements)) {
$nextId = $elements[$nextKey]->getId();
}
else {
$nextId = null;
}
if ($prevKey >= 0) {
$prevId = $elements[$prevKey]->getId();
}
else {
$prevId = null;
}
}
}
if ($this->app['browser']->isMobile()) { if ($this->app['browser']->isMobile()) {
return $this->renderResponse('lightbox/basket_element.html.twig', [ return $this->renderResponse('lightbox/basket_element.html.twig', [
'basket_element' => $basketElement, 'basket_element' => $basketElement,
'module_name' => $basketElement->getRecord($this->app)->get_title() 'module_name' => $basketElement->getRecord($this->app)->get_title(),
'nextId' => $nextId,
'prevId' => $prevId
]); ]);
} }
$basket = $basketElement->getBasket();
$ret = []; $ret = [];
$ret['number'] = $basketElement->getRecord($this->app)->getNumber(); $ret['number'] = $basketElement->getRecord($this->app)->getNumber();

View File

@@ -358,6 +358,10 @@ class EditController extends Controller
$this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record)); $this->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($record));
} }
if (isset($rec['technicalsdatas']) && is_array($rec['technicalsdatas'])){
$record->insertOrUpdateTechnicalDatas($rec['technicalsdatas']);
}
$newstat = $record->getStatus(); $newstat = $record->getStatus();
$statbits = ltrim($statbits, 'x'); $statbits = ltrim($statbits, 'x');
if (!in_array($statbits, ['', 'null'])) { if (!in_array($statbits, ['', 'null'])) {

View File

@@ -36,16 +36,17 @@ class LanguageController
'serverTimeout' => $translator->trans('phraseanet::erreur: La connection au serveur Phraseanet semble etre indisponible'), 'serverTimeout' => $translator->trans('phraseanet::erreur: La connection au serveur Phraseanet semble etre indisponible'),
'serverDisconnected' => $translator->trans('phraseanet::erreur: Votre session est fermee, veuillez vous re-authentifier'), 'serverDisconnected' => $translator->trans('phraseanet::erreur: Votre session est fermee, veuillez vous re-authentifier'),
'hideMessage' => $translator->trans('phraseanet::Ne plus afficher ce message'), 'hideMessage' => $translator->trans('phraseanet::Ne plus afficher ce message'),
'confirmGroup' => $translator->trans('Supprimer egalement les documents rattaches a ces regroupements'), 'confirmGroup' => $translator->trans('Supprimer egalement les documents rattaches a ces regroupements'),
'confirmDelete' => $translator->trans('reponses:: Ces enregistrements vont etre definitivement supprimes et ne pourront etre recuperes. Etes vous sur ?'), 'confirmDelete' => $translator->trans('reponses:: Ces enregistrements vont etre definitivement supprimes et ne pourront etre recuperes. Etes vous sur ?'),
'cancel' => $translator->trans('boutton::annuler'), 'cancel' => $translator->trans('boutton::annuler'),
'deleteTitle' => $translator->trans('boutton::supprimer'), 'deleteTitle' => $translator->trans('boutton::supprimer'),
'deleteRecords' => $translator->trans('Delete records'), 'deleteRecords' => $translator->trans('Delete records'),
'edit_hetero' => $translator->trans('prod::editing valeurs heterogenes, choisir \'remplacer\', \'ajouter\' ou \'annuler\''), 'moveToTrash' => $translator->trans('prod:app trash: title-trash'),
'confirm_abandon' => $translator->trans('prod::editing::annulation: abandonner les modification ?'), 'edit_hetero' => $translator->trans('prod::editing valeurs heterogenes, choisir \'remplacer\', \'ajouter\' ou \'annuler\''),
'loading' => $translator->trans('phraseanet::chargement'), 'confirm_abandon' => $translator->trans('prod::editing::annulation: abandonner les modification ?'),
'valider' => $translator->trans('boutton::valider'), 'loading' => $translator->trans('phraseanet::chargement'),
'annuler' => $translator->trans('boutton::annuler'), 'valider' => $translator->trans('boutton::valider'),
'annuler' => $translator->trans('boutton::annuler'),
'create' => $translator->trans('boutton::creer'), 'create' => $translator->trans('boutton::creer'),
'rechercher' => $translator->trans('boutton::rechercher'), 'rechercher' => $translator->trans('boutton::rechercher'),
'renewRss' => $translator->trans('boutton::renouveller'), 'renewRss' => $translator->trans('boutton::renouveller'),
@@ -96,33 +97,34 @@ class LanguageController
'onlyOneRecord' => $translator->trans('You can choose only one record'), 'onlyOneRecord' => $translator->trans('You can choose only one record'),
'errorAjaxRequest' => $translator->trans('An error occured, please retry'), 'errorAjaxRequest' => $translator->trans('An error occured, please retry'),
'fileBeingDownloaded' => $translator->trans('Some files are being downloaded'), 'fileBeingDownloaded' => $translator->trans('Some files are being downloaded'),
'warning' => $translator->trans('Attention'), 'warning' => $translator->trans('Attention'),
'browserFeatureSupport' => $translator->trans('This feature is not supported by your browser'), 'browserFeatureSupport' => $translator->trans('This feature is not supported by your browser'),
'noActiveBasket' => $translator->trans('No active basket'), 'noActiveBasket' => $translator->trans('No active basket'),
'pushUserCanDownload' => $translator->trans('User can download HD'), 'pushUserCanDownload' => $translator->trans('User can download HD'),
'feedbackCanContribute' => $translator->trans('User contribute to the feedback'), 'feedbackCanContribute' => $translator->trans('User contribute to the feedback'),
'feedbackCanSeeOthers' => $translator->trans('User can see others choices'), 'feedbackCanSeeOthers' => $translator->trans('User can see others choices'),
'forceSendDocument' => $translator->trans('Force sending of the document ?'), 'forceSendDocument' => $translator->trans('Force sending of the document ?'),
'export' => $translator->trans('Export'), 'export' => $translator->trans('Export'),
'share' => $translator->trans('Share'), 'share' => $translator->trans('Share'),
'move' => $translator->trans('Move'), 'move' => $translator->trans('Move'),
'push' => $translator->trans('Push'), 'push' => $translator->trans('Push'),
'feedback' => $translator->trans('Feedback'), 'feedback' => $translator->trans('Feedback'),
'toolbox' => $translator->trans('Tool box'), 'toolbox' => $translator->trans('Tool box'),
'print' => $translator->trans('Print'), 'videoEditor' => $translator->trans('prod:edit: video-editor'),
'attention' => $translator->trans('Attention !'), 'print' => $translator->trans('Print'),
'mapMarkerEdit' => $translator->trans('Edit position'), 'attention' => $translator->trans('Attention !'),
'mapMarkerAdd' => $translator->trans('Add a position'), 'mapMarkerEdit' => $translator->trans('Edit position'),
'mapMarkerMoveLabel' => $translator->trans('Drag and drop the pin to move position'), 'mapMarkerAdd' => $translator->trans('Add a position'),
'mapMarkerEditCancel' => $translator->trans('Cancel'), 'mapMarkerMoveLabel' => $translator->trans('Drag and drop the pin to move position'),
'mapMarkerEditSubmit' => $translator->trans('Submit'), 'mapMarkerEditCancel' => $translator->trans('Cancel'),
'Keyboard shortcuts' => $translator->trans('Keyboard shortcuts'), 'mapMarkerEditSubmit' => $translator->trans('Submit'),
'Play' => $translator->trans('Play'), 'Keyboard shortcuts' => $translator->trans('Keyboard shortcuts'),
'Change play speed' => $translator->trans('Change play speed'), 'Play' => $translator->trans('Play'),
'Pause' => $translator->trans('Pause'), 'Change play speed' => $translator->trans('Change play speed'),
'One frame forward' => $translator->trans('One frame forward'), 'Pause' => $translator->trans('Pause'),
'One frame backward' => $translator->trans('One frame backward'), 'One frame forward' => $translator->trans('One frame forward'),
'Add an entry point' => $translator->trans('Add an entry point'), 'One frame backward' => $translator->trans('One frame backward'),
'Add an entry point' => $translator->trans('Add an entry point'),
'Add an end point' => $translator->trans('Add an end point'), 'Add an end point' => $translator->trans('Add an end point'),
'Navigate to entry point' => $translator->trans('Navigate to entry point'), 'Navigate to entry point' => $translator->trans('Navigate to entry point'),
'Navigate to end point' => $translator->trans('Navigate to end point'), 'Navigate to end point' => $translator->trans('Navigate to end point'),
@@ -130,20 +132,28 @@ class LanguageController
'Toggle loop' => $translator->trans('Toggle loop'), 'Toggle loop' => $translator->trans('Toggle loop'),
'Shift' => $translator->trans('Shift'), 'Shift' => $translator->trans('Shift'),
'Ctrl' => $translator->trans('Ctrl'), 'Ctrl' => $translator->trans('Ctrl'),
'Space bar' => $translator->trans('Space bar'), 'Space bar' => $translator->trans('Space bar'),
'or' => $translator->trans('or'), 'or' => $translator->trans('or'),
'Suppr' => $translator->trans('Suppr'), 'Suppr' => $translator->trans('Suppr'),
'Add new range' => $translator->trans('Add new range'), 'Add new range' => $translator->trans('Add new range'),
'Export ranges' => $translator->trans('Export ranges'), 'Export ranges' => $translator->trans('Export ranges'),
'Start Range' => $translator->trans('Start Range'), 'Start Range' => $translator->trans('Start Range'),
'End Range' => $translator->trans('End Range'), 'End Range' => $translator->trans('End Range'),
'Remove current Range' => $translator->trans('Remove current Range'), 'Remove current Range' => $translator->trans('Remove current Range'),
'Go to start point' => $translator->trans('Go to start point'), 'Go to start point' => $translator->trans('Go to start point'),
'Go 1 frame backward' => $translator->trans('Go 1 frame backward'), 'Go 1 frame backward' => $translator->trans('Go 1 frame backward'),
'Go 1 frame forward' => $translator->trans('Go 1 frame forward'), 'Go 1 frame forward' => $translator->trans('Go 1 frame forward'),
'Go to end point' => $translator->trans('Go to end point'), 'Go to end point' => $translator->trans('Go to end point'),
'Move up range' => $translator->trans('Move up range'), 'Move up range' => $translator->trans('Move up range'),
'Move down range' => $translator->trans('Move down range'), 'Move down range' => $translator->trans('Move down range'),
'error video editor' => $translator->trans('prod:edit: only a media of type video can be edited'),
'Chapters' => $translator->trans('prod:edit: chapters'),
'No hover to chapter' => $translator->trans('prod:edit: no overlaps for chapters'),
'suggested_values' => $translator->trans('prod:edit: suggested_values'),
'title notice' => $translator->trans('prod:mapboxgl: title notice'),
'description notice' => $translator->trans('prod:mapboxgl: description notice'),
'title-map-dialog' => $translator->trans('prod:mapboxgl: title map dialog'),
'create new user' => $translator->trans('prod:push: create new user'),
]); ]);
} }
} }

View File

@@ -225,8 +225,7 @@ class LazaretController extends Controller
$media = $this->app->getMediaFromUri($lazaretFileName); $media = $this->app->getMediaFromUri($lazaretFileName);
$record = $lazaretFile->getCollection($this->app)->get_databox()->get_record($recordId); $record = $lazaretFile->getCollection($this->app)->get_databox()->get_record($recordId);
$this->getSubDefinitionSubstituer() $this->getSubDefinitionSubstituer()->substituteDocument($record, $media);
->substitute($record, 'document', $media);
$this->getDataboxLogger($record->getDatabox())->log( $this->getDataboxLogger($record->getDatabox())->log(
$record, $record,
\Session_Logger::EVENT_SUBSTITUTE, \Session_Logger::EVENT_SUBSTITUTE,

View File

@@ -110,6 +110,9 @@ class MoveCollectionController extends Controller
return $this->app->json($datas); return $this->app->json($datas);
} }
/** @var \collection[] $trashCollectionsBySbasId */
$trashCollectionsBySbasId = [];
foreach ($records as $record) { foreach ($records as $record) {
$record->move_to_collection($collection, $this->getApplicationBox()); $record->move_to_collection($collection, $this->getApplicationBox());
@@ -121,6 +124,33 @@ class MoveCollectionController extends Controller
} }
} }
} }
$sbasId = $record->getDatabox()->get_sbas_id();
if (!array_key_exists($sbasId, $trashCollectionsBySbasId)) {
$trashCollectionsBySbasId[$sbasId] = $record->getDatabox()->getTrashCollection();
}
if ($trashCollectionsBySbasId[$sbasId] !== null) {
if ($record->getCollection()->get_coll_id() == $trashCollectionsBySbasId[$sbasId]->get_coll_id() && $collection->get_coll_id() !== $trashCollectionsBySbasId[$sbasId]->get_coll_id()) {
// record is already in trash so active it
foreach ($record->get_subdefs() as $subdef) {
if (($pl = $subdef->get_permalink())) {
$pl->set_is_activated(true);
}
}
if ($request->request->get("chg_coll_son") == "1") {
/** @var \record_adapter $child */
foreach ($record->getChildren() as $child) {
if ($this->getAclForUser()->has_right_on_base($child->getBaseId(), \ACL::CANDELETERECORD)) {
foreach ($child->get_subdefs() as $childSubdef) {
if (($childPl = $childSubdef->get_permalink())) {
$childPl->set_is_activated(true);
}
}
}
}
}
}
}
} }
$ret = [ $ret = [

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Cache\Exception;
use Alchemy\Phrasea\Collection\Reference\CollectionReference; use Alchemy\Phrasea\Collection\Reference\CollectionReference;
use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Core\Configuration\DisplaySettingService; use Alchemy\Phrasea\Core\Configuration\DisplaySettingService;
use Alchemy\Phrasea\Model\Entities\ElasticsearchRecord;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions; use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContextFactory; use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContextFactory;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
@@ -132,9 +133,9 @@ class QueryController extends Controller
// since the query comes from a submited form, normalize crlf,cr,lf ... // since the query comes from a submited form, normalize crlf,cr,lf ...
$query = StringHelper::crlfNormalize($query); $query = StringHelper::crlfNormalize($query);
$json = array( $json = [
'query' => $query 'query' => $query
); ];
$options = SearchEngineOptions::fromRequest($this->app, $request); $options = SearchEngineOptions::fromRequest($this->app, $request);
@@ -168,7 +169,7 @@ class QueryController extends Controller
foreach ($collectionsReferencesByDatabox as $sbid => $references) { foreach ($collectionsReferencesByDatabox as $sbid => $references) {
$databox = $this->findDataboxById($sbid); $databox = $this->findDataboxById($sbid);
$collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references); $collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references);
$this->getSearchEngineLogger()->log($databox, $result->getUserQuery(), $result->getTotal(), $collectionsIds); $this->getSearchEngineLogger()->log($databox, $result->getQueryText(), $result->getTotal(), $collectionsIds);
} }
$proposals = $firstPage ? $result->getProposals() : false; $proposals = $firstPage ? $result->getProposals() : false;
@@ -177,6 +178,8 @@ class QueryController extends Controller
$page = $result->getCurrentPage($perPage); $page = $result->getCurrentPage($perPage);
$queryESLib = $result->getQueryESLib();
$string = ''; $string = '';
if ($npages > 1) { if ($npages > 1) {
@@ -229,23 +232,19 @@ class QueryController extends Controller
} }
$string .= '<div style="display:none;"><div id="NEXT_PAGE"></div><div id="PREV_PAGE"></div></div>'; $string .= '<div style="display:none;"><div id="NEXT_PAGE"></div><div id="PREV_PAGE"></div></div>';
$explain = "<div id=\"explainResults\" class=\"myexplain\">"; $explain = $this->render(
"prod/results/infos.html.twig",
$explain .= "<img src=\"/assets/common/images/icons/answers.gif\" /><span><b>"; [
'results'=> $result,
if ($result->getTotal() != $result->getAvailable()) { 'esquery' => $this->getAclForUser()->is_admin() ?
$explain .= $this->app->trans('reponses:: %available% Resultats rappatries sur un total de %total% trouves', ['available' => $result->getAvailable(), '%total%' => $result->getTotal()]); json_encode($queryESLib['body'], JSON_PRETTY_PRINT | JSON_HEX_TAG | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES) :
} else { null
$explain .= $this->app->trans('reponses:: %total% Resultats', ['%total%' => $result->getTotal()]); ]
} );
$explain .= " </b></span>";
$explain .= '<br><div>' . ($result->getDuration() / 1000) . ' s</div>dans index ' . $result->getIndexes();
$explain .= "</div>";
$infoResult = '<div id="docInfo">' $infoResult = '<div id="docInfo">'
. $this->app->trans('%number% documents<br/>selectionnes', ['%number%' => '<span id="nbrecsel"></span>']) . $this->app->trans('%number% documents<br/>selectionnes', ['%number%' => '<span id="nbrecsel"></span>'])
. '</div><a href="#" class="infoDialog search-display-info" data-infos="' . str_replace('"', '&quot;', $explain) . '">' . '</div><a href="#" class="search-display-info" data-infos="' . str_replace('"', '&quot;', $explain) . '">'
. $this->app->trans('%total% reponses', ['%total%' => '<span>'.$result->getTotal().'</span>']) . '</a>'; . $this->app->trans('%total% reponses', ['%total%' => '<span>'.$result->getTotal().'</span>']) . '</a>';
$json['infos'] = $infoResult; $json['infos'] = $infoResult;
@@ -274,29 +273,69 @@ class QueryController extends Controller
} else { } else {
$template = 'prod/results/records.html.twig'; $template = 'prod/results/records.html.twig';
} }
$json['results'] = $this->render($template, ['results'=> $result]); $json['results'] = $this->render($template, ['results'=> $result]);
/** Debug */
$json['parsed_query'] = $result->getEngineQuery();
/** End debug */
$fieldLabels = [];
// add technical fields // add technical fields
$fieldLabels = [];
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) { foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
$fieldLabels[$k] = $this->app->trans($f['label']); $fieldLabels[$k] = $this->app->trans($f['label']);
} }
// add databox fields // add databox fields
// get infos about fields, fusionned and by databox
$fieldsInfos = []; // by databox
foreach ($this->app->getDataboxes() as $databox) { foreach ($this->app->getDataboxes() as $databox) {
$sbasId = $databox->get_sbas_id();
$fieldsInfos[$sbasId] = [];
foreach ($databox->get_meta_structure() as $field) { foreach ($databox->get_meta_structure() as $field) {
if (!isset($fieldLabels[$field->get_name()])) { $name = $field->get_name();
$fieldLabels[$field->get_name()] = $field->get_label($this->app['locale']); $fieldsInfos[$sbasId][$name] = [
'label' => $field->get_label($this->app['locale']),
'type' => $field->get_type(),
'business' => $field->isBusiness(),
'multi' => $field->is_multi(),
];
if (!isset($fieldLabels[$name])) {
$fieldLabels[$name] = $field->get_label($this->app['locale']);
} }
} }
} }
$facets = []; // populates fileds infos
$json['fields'] = $fieldsInfos;
// populates rawresults
// need acl so the result will not include business fields where not allowed
$acl = $this->getAclForUser();
$json['rawResults'] = [];
/** @var ElasticsearchRecord $record */
foreach($result->getResults() as $record) {
$rawRecord = $record->asArray();
$sbasId = $record->getDataboxId();
$baseId = $record->getBaseId();
$caption = $rawRecord['caption'];
if($acl && $acl->has_right_on_base($baseId, \ACL::CANMODIFRECORD)) {
$caption = array_merge($caption, $rawRecord['privateCaption']);
}
// read the fields following the structure order
$rawCaption = [];
foreach($fieldsInfos[$sbasId] as $fieldName=>$fieldInfos) {
if(array_key_exists($fieldName, $caption)) {
$rawCaption[$fieldName] = $caption[$fieldName];
}
}
$rawRecord['caption'] = $rawCaption;
unset($rawRecord['privateCaption']);
$json['rawResults'][$record->getId()] = $rawRecord;
}
// populates facets (aggregates)
$facets = [];
foreach ($result->getFacets() as $facet) { foreach ($result->getFacets() as $facet) {
$facetName = $facet['name']; $facetName = $facet['name'];
@@ -311,6 +350,9 @@ class QueryController extends Controller
$json['next_page'] = ($page < $npages && $result->getAvailable() > 0) ? ($page + 1) : false; $json['next_page'] = ($page < $npages && $result->getAvailable() > 0) ? ($page + 1) : false;
$json['prev_page'] = ($page > 1 && $result->getAvailable() > 0) ? ($page - 1) : false; $json['prev_page'] = ($page > 1 && $result->getAvailable() > 0) ? ($page - 1) : false;
$json['form'] = $options->serialize(); $json['form'] = $options->serialize();
$json['queryCompiled'] = $result->getQueryCompiled();
$json['queryAST'] = $result->getQueryAST();
$json['queryESLib'] = $queryESLib;
} }
catch(\Exception $e) { catch(\Exception $e) {
// we'd like a message from the parser so get all the exceptions messages // we'd like a message from the parser so get all the exceptions messages
@@ -319,9 +361,9 @@ class QueryController extends Controller
$msg .= ($msg ? "\n":"") . $e->getMessage(); $msg .= ($msg ? "\n":"") . $e->getMessage();
} }
$template = 'prod/results/help.html.twig'; $template = 'prod/results/help.html.twig';
$result = array( $result = [
'error' => $msg 'error' => $msg
); ];
$json['results'] = $this->render($template, ['results'=> $result]); $json['results'] = $this->render($template, ['results'=> $result]);
} }

View File

@@ -14,6 +14,8 @@ use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Application\Helper\SearchEngineAware; use Alchemy\Phrasea\Application\Helper\SearchEngineAware;
use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Controller\RecordsRequest; use Alchemy\Phrasea\Controller\RecordsRequest;
use Alchemy\Phrasea\Core\Event\RecordEdit;
use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Model\Entities\BasketElement; use Alchemy\Phrasea\Model\Entities\BasketElement;
use Alchemy\Phrasea\Model\Repositories\BasketElementRepository; use Alchemy\Phrasea\Model\Repositories\BasketElementRepository;
use Alchemy\Phrasea\Model\Repositories\StoryWZRepository; use Alchemy\Phrasea\Model\Repositories\StoryWZRepository;
@@ -86,6 +88,7 @@ class RecordController extends Controller
// get field's values // get field's values
$recordCaptions[$field->get_name()] = $field->get_serialized_values(); $recordCaptions[$field->get_name()] = $field->get_serialized_values();
} }
$recordCaptions["technicalInfo"] = $record->getPositionFromTechnicalInfos();
return $this->app->json([ return $this->app->json([
"desc" => $this->render('prod/preview/caption.html.twig', [ "desc" => $this->render('prod/preview/caption.html.twig', [
@@ -189,7 +192,12 @@ class RecordController extends Controller
$deleted = []; $deleted = [];
/** @var \collection[] $trashCollectionsBySbasId */
$trashCollectionsBySbasId = [];
$manager = $this->getEntityManager(); $manager = $this->getEntityManager();
/** @var \record_adapter $record */
foreach ($records as $record) { foreach ($records as $record) {
try { try {
$basketElements = $basketElementsRepository->findElementsByRecord($record); $basketElements = $basketElementsRepository->findElementsByRecord($record);
@@ -205,10 +213,34 @@ class RecordController extends Controller
$manager->remove($attachedStory); $manager->remove($attachedStory);
} }
$deleted[] = $record->getId(); foreach($record->get_grouping_parents() as $story) {
$record->delete(); $this->getEventDispatcher()->dispatch(PhraseaEvents::RECORD_EDIT, new RecordEdit($story));
} catch (\Exception $e) { }
$sbasId = $record->getDatabox()->get_sbas_id();
if(!array_key_exists($sbasId, $trashCollectionsBySbasId)) {
$trashCollectionsBySbasId[$sbasId] = $record->getDatabox()->getTrashCollection();
}
$deleted[] = $record->getId();
if($trashCollectionsBySbasId[$sbasId] !== null) {
if($record->getCollection()->get_coll_id() == $trashCollectionsBySbasId[$sbasId]->get_coll_id()) {
// record is already in trash so delete it
$record->delete();
} else {
// move to trash collection
$record->move_to_collection($trashCollectionsBySbasId[$sbasId], $this->getApplicationBox());
// disable permalinks
foreach($record->get_subdefs() as $subdef) {
if( ($pl = $subdef->get_permalink()) ) {
$pl->set_is_activated(false);
}
}
}
} else {
// no trash collection, delete
$record->delete();
}
} catch (\Exception $e) {
} }
} }
@@ -248,9 +280,46 @@ class RecordController extends Controller
[\ACL::CANDELETERECORD] [\ACL::CANDELETERECORD]
); );
return $this->render('prod/actions/delete_records_confirm.html.twig', [ $filteredRecord = $this->filterRecordToDelete($records);
'records' => $records,
return $this->app->json([
'renderView' => $this->render('prod/actions/delete_records_confirm.html.twig', [
'records' => $records,
'filteredRecord' => $filteredRecord
]),
'filteredRecord' => $filteredRecord
]); ]);
}
private function filterRecordToDelete(RecordsRequest $records)
{
$trashCollectionsBySbasId = [];
$goingToTrash = [];
$delete = [];
foreach ($records as $record) {
$sbasId = $record->getDatabox()->get_sbas_id();
if (!array_key_exists($sbasId, $trashCollectionsBySbasId)) {
$trashCollectionsBySbasId[$sbasId] = $record->getDatabox()->getTrashCollection();
}
if ($trashCollectionsBySbasId[$sbasId] !== null) {
if ($record->getCollection()->get_coll_id() == $trashCollectionsBySbasId[$sbasId]->get_coll_id()) {
// record is already in trash
$delete[] = $record;
}
else {
// will be moved to trash
$goingToTrash[] = $record;
}
}
else {
// trash does not exist
$delete[] = $record;
}
}
//check if all values in array are true
//return (!in_array(false, $goingToTrash, true));
return ['trash' => $goingToTrash, 'delete' => $delete];
} }
/** /**
@@ -271,4 +340,12 @@ class RecordController extends Controller
return $this->app->json($renewed); return $this->app->json($renewed);
} }
/**
* @return EventDispatcherInterface
*/
private function getEventDispatcher()
{
return $this->app['dispatcher'];
}
} }

View File

@@ -0,0 +1,61 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Controller\Prod;
use Alchemy\Phrasea\Controller\Controller;
use PHPExiftool\Driver\Metadata\Metadata;
use PHPExiftool\Driver\Metadata\MetadataBag;
use PHPExiftool\Driver\TagFactory;
use PHPExiftool\Driver\Value\Mono;
use PHPExiftool\Reader;
class SubdefsController extends Controller
{
public function metadataAction($databox_id, $record_id, $subdef_name)
{
$record = new \record_adapter($this->app, (int) $databox_id, (int) $record_id);
$metadataBag = new MetadataBag();
try {
$fileEntity = $this->getExifToolReader()
->files($record->get_subdef($subdef_name)->getRealPath())
->first();
$metadatas = $fileEntity->getMetadatas();
foreach($metadatas as $metadata){
$valuedata = $fileEntity->executeQuery($metadata->getTag()->getTagname()."[not(@rdf:datatype = 'http://www.w3.org/2001/XMLSchema#base64Binary')]");
if(empty($valuedata)){
$valuedata = new Mono($this->app->trans('Binary data'));
$tag = TagFactory::getFromRDFTagname($metadata->getTag()->getTagname());
$metadataBagElement = new Metadata($tag, $valuedata);
$metadataBag->set($metadata->getTag()->getTagname(), $metadataBagElement);
}else{
$metadataBag->set($metadata->getTag()->getTagname(), $metadata);
}
}
} catch (PHPExiftoolException $e) {
// ignore
} catch (\Exception_Media_SubdefNotFound $e) {
// ignore
}
return $this->render('prod/actions/Tools/metadata.html.twig', [
'record' => $record,
'metadatas' => $metadataBag,
]);
}
/**
* @return Reader
*/
private function getExifToolReader()
{
return $this->app['exiftool.reader'];
}
}

View File

@@ -23,7 +23,6 @@ use Alchemy\Phrasea\Record\RecordWasRotated;
use DataURI\Parser; use DataURI\Parser;
use MediaAlchemyst\Alchemyst; use MediaAlchemyst\Alchemyst;
use MediaVorus\MediaVorus; use MediaVorus\MediaVorus;
use PHPExiftool\Exception\ExceptionInterface as PHPExiftoolException;
use PHPExiftool\Reader; use PHPExiftool\Reader;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -38,13 +37,14 @@ class ToolsController extends Controller
{ {
$records = RecordsRequest::fromRequest($this->app, $request, false); $records = RecordsRequest::fromRequest($this->app, $request, false);
$metadata = false; $metadatas = false;
$record = null; $record = null;
$recordAccessibleSubdefs = array(); $recordAccessibleSubdefs = array();
if (count($records) == 1) { if (count($records) == 1) {
/** @var \record_adapter $record */ /** @var \record_adapter $record */
$record = $records->first(); $record = $records->first();
$databox = $record->getDatabox();
// fetch subdef list: // fetch subdef list:
$subdefs = $record->get_subdefs(); $subdefs = $record->get_subdefs();
@@ -82,27 +82,17 @@ class ToolsController extends Controller
); );
} }
} }
if (!$record->isStory()) { if (!$record->isStory()) {
try { $metadatas = true;
$metadata = $this->getExifToolReader()
->files($record->get_subdef('document')->getRealPath())
->first()->getMetadatas();
} catch (PHPExiftoolException $e) {
// ignore
} catch (\Exception_Media_SubdefNotFound $e) {
// ignore
}
} }
} }
$conf = $this->getConf(); $conf = $this->getConf();
return $this->render('prod/actions/Tools/index.html.twig', [ return $this->render('prod/actions/Tools/index.html.twig', [
'records' => $records, 'records' => $records,
'record' => $record, 'record' => $record,
'videoEditorConfig' => $conf->get(['video-editor']), 'recordSubdefs' => $recordAccessibleSubdefs,
'recordSubdefs' => $recordAccessibleSubdefs, 'metadatas' => $metadatas,
'metadatas' => $metadata,
]); ]);
} }
@@ -202,7 +192,7 @@ class ToolsController extends Controller
$media = $this->app->getMediaFromUri($tempoFile); $media = $this->app->getMediaFromUri($tempoFile);
$this->getSubDefinitionSubstituer()->substitute($record, 'document', $media); $this->getSubDefinitionSubstituer()->substituteDocument($record, $media);
$record->insertTechnicalDatas($this->getMediaVorus()); $record->insertTechnicalDatas($this->getMediaVorus());
$this->getMetadataSetter()->replaceMetadata($this->getMetadataReader() ->read($media), $record); $this->getMetadataSetter()->replaceMetadata($this->getMetadataReader() ->read($media), $record);
@@ -262,7 +252,7 @@ class ToolsController extends Controller
$media = $this->app->getMediaFromUri($tempoFile); $media = $this->app->getMediaFromUri($tempoFile);
$this->getSubDefinitionSubstituer()->substitute($record, 'thumbnail', $media); $this->getSubDefinitionSubstituer()->substituteSubdef($record, 'thumbnail', $media);
$this->getDataboxLogger($record->getDatabox()) $this->getDataboxLogger($record->getDatabox())
->log($record, \Session_Logger::EVENT_SUBSTITUTE, 'thumbnail', ''); ->log($record, \Session_Logger::EVENT_SUBSTITUTE, 'thumbnail', '');
@@ -426,11 +416,81 @@ class ToolsController extends Controller
$media = $this->app->getMediaFromUri($fileName); $media = $this->app->getMediaFromUri($fileName);
$this->getSubDefinitionSubstituer()->substitute($record, $subDefName, $media); if($subDefName == 'document') {
$this->getSubDefinitionSubstituer()->substituteDocument($record, $media);
} else {
$this->getSubDefinitionSubstituer()->substituteSubdef($record, $subDefName, $media);
}
$this->getDataboxLogger($record->getDatabox()) $this->getDataboxLogger($record->getDatabox())
->log($record, \Session_Logger::EVENT_SUBSTITUTE, $subDefName, ''); ->log($record, \Session_Logger::EVENT_SUBSTITUTE, $subDefName, '');
unset($media); unset($media);
$this->getFilesystem()->remove($fileName); $this->getFilesystem()->remove($fileName);
} }
/**
* @param $request
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function saveMetasAction(Request $request)
{
$record = new \record_adapter($this->app,
(int)$request->request->get("databox_id"),
(int)$request->request->get("record_id"));
$metadatas[0] = [
'meta_struct_id' => (int)$request->request->get("meta_struct_id"),
'meta_id' => '',
'value' => $request->request->get("value")
];
try {
$record->set_metadatas($metadatas);
}
catch (Exception $e) {
return $this->app->json(['success' => false, 'errorMessage' => $e->getMessage()]);
}
return $this->app->json(['success' => true, 'errorMessage' => '']);
}
public function videoEditorAction(Request $request)
{
$records = RecordsRequest::fromRequest($this->app, $request, false);
$metadatas = false;
$record = null;
$JSFields = [];
if (count($records) == 1) {
/** @var \record_adapter $record */
$record = $records->first();
$databox = $record->getDatabox();
foreach ($databox->get_meta_structure() as $meta) {
/** @var \databox_field $meta */
$fields[] = $meta;
/** @Ignore */
$JSFields[$meta->get_id()] = [
'id' => $meta->get_id(),
'name' => $meta->get_name(),
'_value' => $record->getCaption([$meta->get_name()]),
];
}
if (!$record->isStory()) {
$metadatas = true;
}
}
$conf = $this->getConf();
return $this->render('prod/actions/Tools/videoEditor.html.twig', [
'records' => $records,
'record' => $record,
'videoEditorConfig' => $conf->get(['video-editor']),
'metadatas' => $metadatas,
'JSonFields' => json_encode($JSFields),
]);
}
} }

View File

@@ -193,7 +193,7 @@ class UploadController extends Controller
file_put_contents($fileName, $dataUri->getData()); file_put_contents($fileName, $dataUri->getData());
$media = $this->app->getMediaFromUri($fileName); $media = $this->app->getMediaFromUri($fileName);
$this->getSubDefinitionSubstituer()->substitute($elementCreated, 'thumbnail', $media); $this->getSubDefinitionSubstituer()->substituteSubdef($elementCreated, 'thumbnail', $media);
$this->getDataboxLogger($elementCreated->getDatabox()) $this->getDataboxLogger($elementCreated->getDatabox())
->log($elementCreated, \Session_Logger::EVENT_SUBSTITUTE, 'thumbnail', ''); ->log($elementCreated, \Session_Logger::EVENT_SUBSTITUTE, 'thumbnail', '');

View File

@@ -552,6 +552,7 @@ class LoginController extends Controller
} while (null !== $this->getUserRepository()->findOneBy(['login' => $login])); } while (null !== $this->getUserRepository()->findOneBy(['login' => $login]));
$user = $this->getUserManipulator()->createUser($login, $this->getStringGenerator()->generateString(128)); $user = $this->getUserManipulator()->createUser($login, $this->getStringGenerator()->generateString(128));
$user->setGuest(true);
$invite_user = $this->getUserRepository()->findByLogin(User::USER_GUEST); $invite_user = $this->getUserRepository()->findByLogin(User::USER_GUEST);
$usr_base_ids = array_keys($this->getAclForUser($user)->get_granted_base()); $usr_base_ids = array_keys($this->getAclForUser($user)->get_granted_base());

View File

@@ -11,6 +11,7 @@
namespace Alchemy\Phrasea\Controller; namespace Alchemy\Phrasea\Controller;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\StructureTemplate;
use Alchemy\Phrasea\Setup\RequirementCollectionInterface; use Alchemy\Phrasea\Setup\RequirementCollectionInterface;
use Alchemy\Phrasea\Setup\Requirements\BinariesRequirements; use Alchemy\Phrasea\Setup\Requirements\BinariesRequirements;
use Alchemy\Phrasea\Setup\Requirements\FilesystemRequirements; use Alchemy\Phrasea\Setup\Requirements\FilesystemRequirements;
@@ -74,10 +75,13 @@ class SetupController extends Controller
$warnings[] = $this->app->trans('It is not recommended to install Phraseanet without HTTPS support'); $warnings[] = $this->app->trans('It is not recommended to install Phraseanet without HTTPS support');
} }
/** @var StructureTemplate $st */
$st = $this->app['phraseanet.structure-template'];
return $this->render('/setup/step2.html.twig', [ return $this->render('/setup/step2.html.twig', [
'locale' => $this->app['locale'], 'locale' => $this->app['locale'],
'available_locales' => Application::getAvailableLanguages(), 'available_locales' => Application::getAvailableLanguages(),
'available_templates' => $this->app['phraseanet.structure-template']->getAvailable()->getTemplates(), 'available_templates' => $st->getNames(),
'warnings' => $warnings, 'warnings' => $warnings,
'error' => $request->query->get('error'), 'error' => $request->query->get('error'),
'current_servername' => $request->getScheme() . '://' . $request->getHttpHost() . '/', 'current_servername' => $request->getScheme() . '://' . $request->getHttpHost() . '/',
@@ -92,7 +96,7 @@ class SetupController extends Controller
$servername = $request->getScheme() . '://' . $request->getHttpHost() . '/'; $servername = $request->getScheme() . '://' . $request->getHttpHost() . '/';
$dbConn = null; $dbConn = null;
$database_host = $request->request->get('hostname'); $database_host = $request->request->get('hostname');
$database_port = $request->request->get('port'); $database_port = $request->request->get('port');

View File

@@ -18,9 +18,12 @@ use Silex\Application;
use Silex\ControllerCollection; use Silex\ControllerCollection;
use Silex\ControllerProviderInterface; use Silex\ControllerProviderInterface;
use Silex\ServiceProviderInterface; use Silex\ServiceProviderInterface;
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
class OAuth2 extends Api implements ControllerProviderInterface, ServiceProviderInterface class OAuth2 extends Api implements ControllerProviderInterface, ServiceProviderInterface
{ {
use ControllerProviderTrait;
public function register(Application $app) public function register(Application $app)
{ {
$app['controller.oauth2'] = $app->share(function (PhraseaApplication $app) { $app['controller.oauth2'] = $app->share(function (PhraseaApplication $app) {
@@ -35,6 +38,16 @@ class OAuth2 extends Api implements ControllerProviderInterface, ServiceProvider
public function connect(Application $app) public function connect(Application $app)
{ {
$firewall = $this->getFirewall($app);
$requireUnauthenticated = function () use ($firewall) {
if (null !== $response = $firewall->requireNotAuthenticated()) {
return $response;
}
return null;
};
if (! $this->isApiEnabled($app)) { if (! $this->isApiEnabled($app)) {
return $app['controllers_factory']; return $app['controllers_factory'];
} }
@@ -48,6 +61,15 @@ class OAuth2 extends Api implements ControllerProviderInterface, ServiceProvider
$controllers->post('/token', 'controller.oauth2:tokenAction'); $controllers->post('/token', 'controller.oauth2:tokenAction');
$controllers->get('/provider/{providerId}/authorize/', 'controller.oauth2:authorizeWithProviderAction')
->before($requireUnauthenticated)
->bind('oauth2_provider_authorize');
// AuthProviders callbacks
$controllers->get('/provider/{providerId}/callback/', 'controller.oauth2:authorizeCallbackAction')
->before($requireUnauthenticated)
->bind('login_authentication_provider_callback');
return $controllers; return $controllers;
} }
} }

View File

@@ -37,6 +37,7 @@ class V1 extends Api implements ControllerProviderInterface, ServiceProviderInte
return (new V1Controller($app)) return (new V1Controller($app))
->setDataboxLoggerLocator($app['phraseanet.logger']) ->setDataboxLoggerLocator($app['phraseanet.logger'])
->setDispatcher($app['dispatcher']) ->setDispatcher($app['dispatcher'])
->setFileSystemLocator(new LazyLocator($app, 'filesystem'))
->setJsonBodyHelper(new LazyLocator($app, 'json.body_helper')); ->setJsonBodyHelper(new LazyLocator($app, 'json.body_helper'));
}); });
} }
@@ -263,6 +264,9 @@ class V1 extends Api implements ControllerProviderInterface, ServiceProviderInte
$controllers->get('/me/', 'controller.api.v1:getCurrentUserAction'); $controllers->get('/me/', 'controller.api.v1:getCurrentUserAction');
$controllers->delete('/me/', 'controller.api.v1:deleteCurrentUserAction'); $controllers->delete('/me/', 'controller.api.v1:deleteCurrentUserAction');
$controllers->get('/me/structures/', 'controller.api.v1:getCurrentUserStructureAction');
$controllers->get('/me/subdefs/', 'controller.api.v1:getCurrentUserSubdefsAction');
$controllers->get('/me/collections/', 'controller.api.v1:getCurrentUserCollectionsAction');
$controllers->post('/me/request-collections/', 'controller.api.v1:createCollectionRequests'); $controllers->post('/me/request-collections/', 'controller.api.v1:createCollectionRequests');
$controllers->post('/me/update-account/', 'controller.api.v1:updateCurrentUserAction'); $controllers->post('/me/update-account/', 'controller.api.v1:updateCurrentUserAction');

View File

@@ -80,6 +80,7 @@ class ControllerProviderServiceProvider implements ServiceProviderInterface
Prod\Root::class => [], Prod\Root::class => [],
Prod\Share::class => [], Prod\Share::class => [],
Prod\Story::class => [], Prod\Story::class => [],
Prod\Subdefs::class => [],
Prod\Tools::class => [], Prod\Tools::class => [],
Prod\Tooltip::class => [], Prod\Tooltip::class => [],
Prod\TOU::class => [], Prod\TOU::class => [],

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\ControllerProvider\Prod;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Controller\Prod\SubdefsController;
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
use Silex\ControllerProviderInterface;
use Silex\ServiceProviderInterface;
use Silex\Application;
class Subdefs implements ControllerProviderInterface, ServiceProviderInterface
{
use ControllerProviderTrait;
public function register(Application $app)
{
$app['controller.prod.subdefs'] = $app->share(function (PhraseaApplication $app) {
return (new SubdefsController($app));
});
}
public function boot(Application $app)
{
// no-op
}
public function connect(Application $app)
{
$controllers = $this->createAuthenticatedCollection($app);
$controllers->get('/{databox_id}/{record_id}/metadatas/{subdef_name}/', 'controller.prod.subdefs:metadataAction')
->bind('prod_subdefs_metadata');
return $controllers;
}
}

View File

@@ -69,6 +69,11 @@ class Tools implements ControllerProviderInterface, ServiceProviderInterface
$controllers->post('/sharing-editor/{base_id}/{record_id}/', 'controller.prod.tools:editRecordSharing'); $controllers->post('/sharing-editor/{base_id}/{record_id}/', 'controller.prod.tools:editRecordSharing');
$controllers->post('/metadata/save/', 'controller.prod.tools:saveMetasAction')
->bind('prod_tools_metadata_save');
$controllers->get('/videoEditor', 'controller.prod.tools:videoEditorAction');
return $controllers; return $controllers;
} }
} }

View File

@@ -18,6 +18,8 @@ class DisplaySettingService
const ORDER_ALPHA_ASC = "ORDER_ALPHA_ASC"; const ORDER_ALPHA_ASC = "ORDER_ALPHA_ASC";
const ORDER_ALPHA_DESC = "ORDER_ALPHA_DESC"; const ORDER_ALPHA_DESC = "ORDER_ALPHA_DESC";
const ORDER_BY_ADMIN = "ORDER_BY_ADMIN"; const ORDER_BY_ADMIN = "ORDER_BY_ADMIN";
const ORDER_BY_BCT = "ORDER_BY_BCT";
const ORDER_BY_HITS = "ORDER_BY_HITS";
/** /**
* The default user settings. * The default user settings.

View File

@@ -82,13 +82,16 @@ class RegistryFormManipulator
private function filterNullValues(array &$array) private function filterNullValues(array &$array)
{ {
return array_filter($array, function (&$value) { foreach ($array as $key => &$value) {
if (is_array($value)) { if (is_array($value)) {
$value = $this->filterNullValues($value); $this->filterNullValues($value);
} }
else if ($key !== 'geonames-server' && $value === null) {
unset($array[$key]);
}
}
return null !== $value; return $array;
});
} }
private function getDefaultData(array $config) private function getDefaultData(array $config)

View File

@@ -10,8 +10,6 @@
namespace Alchemy\Phrasea\Core\Configuration; namespace Alchemy\Phrasea\Core\Configuration;
use Alchemy\Phrasea\Application;
/** /**
* Class StructureTemplate * Class StructureTemplate
* @package Alchemy\Phrasea\Core\Configuration * @package Alchemy\Phrasea\Core\Configuration
@@ -19,25 +17,36 @@ use Alchemy\Phrasea\Application;
class StructureTemplate class StructureTemplate
{ {
const TEMPLATE_EXTENSION = 'xml'; const TEMPLATE_EXTENSION = 'xml';
private $templates; const DEFAULT_TEMPLATE = 'en-simple';
public function __construct(Application $app) /** @var string */
{ private $rootPath;
$this->app = $app;
} /** @var \SplFileInfo[] */
private $templates;
/** @var string[] */
private $names;
/** /**
* @return $this * @param string $rootPath
* @throws \Exception
*/ */
public function getAvailable() public function __construct($rootPath)
{ {
$templateList = new \DirectoryIterator($this->app['root.path'] . '/lib/conf.d/data_templates'); $this->rootPath = $rootPath;
if (empty($templateList)) { $this->names = $this->templates = null; // lazy loaded, not yet set
throw new \Exception('No available structure template'); }
private function load()
{
if(!is_null($this->templates)) {
return; // already loaded
} }
$templates = [];
$abbreviationLength = 2; $templateList = new \DirectoryIterator($this->rootPath . '/lib/conf.d/data_templates');
$this->templates = [];
$this->names = [];
foreach ($templateList as $template) { foreach ($templateList as $template) {
if ($template->isDot() if ($template->isDot()
|| !$template->isFile() || !$template->isFile()
@@ -45,65 +54,64 @@ class StructureTemplate
) { ) {
continue; continue;
} }
$name = $template->getFilename();
$abbreviation = strtolower(substr($name, 0, $abbreviationLength));
if (array_key_exists($abbreviation, $templates)) {
$abbreviation = strtolower(substr($name, 0, ++$abbreviationLength));
}
$templates[$abbreviation] = $template->getBasename('.' . self::TEMPLATE_EXTENSION);
}
$this->templates = $templates;
return $this; $name = $template->getBasename('.' . self::TEMPLATE_EXTENSION);
// beware that the directoryiterator returns a reference on a static, so clone()
$this->templates[$name] = clone($template);
$this->names[] = $name;
}
}
/**
* @param $templateName
* @return null|\SplFileInfo
*/
public function getByName($templateName)
{
$this->load();
if (!array_key_exists($templateName, $this->templates)) {
return null;
}
return $this->templates[$templateName];
}
/**
* @param $index
* @return null|\SplFileInfo
*/
public function getNameByIndex($index)
{
$this->load();
return $this->names[$index];
}
/**
* @return \string[]
*/
public function getNames()
{
$this->load();
return $this->names;
}
public function toString()
{
$this->load();
return implode(', ', $this->names);
} }
/** /**
* @return string * @return string
*/ */
public function __toString() public function getDefault()
{ {
if (!$this->templates) { $this->load();
return '';
}
$templateToString = '';
$cpt = 1;
$templateLength = count($this->templates);
foreach ($this->templates as $key => $value) {
if (($templateLength - 1) == $cpt) {
$separator = ' and ';
}
elseif (end($this->templates) == $value) {
$separator = '';
}
else {
$separator = ', ';
}
$templateToString .= $key . ' (' . $value . ')' . $separator;
$cpt++;
}
return $templateToString; return $this->getByName(self::DEFAULT_TEMPLATE) ? self::DEFAULT_TEMPLATE : $this->getNameByIndex(0);
}
/**
* @param $template
* @return mixed
* @throws \Exception
*/
public function getTemplateName($template = 'en')
{
if (!array_key_exists($template, $this->templates)) {
throw new \Exception('Not found template : ' . $template);
}
return $this->templates[$template];
}
/**
* @return mixed
*/
public function getTemplates()
{
return $this->templates;
} }
} }

View File

@@ -109,7 +109,7 @@ class RegistrationSubscriber extends AbstractNotificationSubscriber
$body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur nom'), $registeredUser->getFirstName()); $body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur nom'), $registeredUser->getFirstName());
$body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur prenom'), $registeredUser->getLastName()); $body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur prenom'), $registeredUser->getLastName());
$body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur email'), $registeredUser->getEmail()); $body .= sprintf("%s : %s\n", $this->app->trans('admin::compte-utilisateur email'), $registeredUser->getEmail());
$body .= sprintf("%s/%s\n", $registeredUser->get_job(), $registeredUser->getCompany()); $body .= sprintf("%s/%s\n", $registeredUser->getJob(), $registeredUser->getCompany());
$readyToSend = false; $readyToSend = false;
try { try {

View File

@@ -75,7 +75,7 @@ class ConfigurationServiceProvider implements ServiceProviderInterface
}); });
$app['phraseanet.structure-template'] = $app->share(function (Application $app) { $app['phraseanet.structure-template'] = $app->share(function (Application $app) {
return new StructureTemplate($app); return new StructureTemplate($app['root.path']);
}); });
} }

View File

@@ -146,6 +146,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
$app['elasticsearch.indexer.databox_fetcher_factory'] = $app->share(function ($app) { $app['elasticsearch.indexer.databox_fetcher_factory'] = $app->share(function ($app) {
return new DataboxFetcherFactory( return new DataboxFetcherFactory(
$app['elasticsearch.record_helper'], $app['elasticsearch.record_helper'],
$app['elasticsearch.options'],
$app, $app,
'search_engine.structure', 'search_engine.structure',
'thesaurus' 'thesaurus'

View File

@@ -127,6 +127,23 @@ class TwigServiceProvider implements ServiceProviderInterface
); );
}, ['needs_environment' => true, 'is_safe' => ['html']])); }, ['needs_environment' => true, 'is_safe' => ['html']]));
$twig->addFilter(new \Twig_SimpleFilter('parseColor', function (\Twig_Environment $twig, $string) use ($app) {
$re = '/^(.*)\[#([0-9a-fA-F]{6})]$/m';
$stringArr = explode(';', $string);
foreach ($stringArr as $key => $value) {
preg_match_all($re, trim($value), $matches);
if ($matches && $matches[1] != null && $matches[2] != null) {
$colorCode = '#' . $matches[2][0];
$colorName = $matches[1][0];
$stringArr[$key] = '<span style="white-space: nowrap;"><span class="color-dot" style="margin-right: 4px; background-color: ' . $colorCode . '"></span>' . $colorName . '</span>';
}
}
return implode('; ', $stringArr);
}, ['needs_environment' => true, 'is_safe' => ['html']]));
$twig->addFilter(new \Twig_SimpleFilter('bounce', $twig->addFilter(new \Twig_SimpleFilter('bounce',
function (\Twig_Environment $twig, $fieldValue, $fieldName, $searchRequest, $sbasId) { function (\Twig_Environment $twig, $fieldValue, $fieldName, $searchRequest, $sbasId) {
// bounce value if it is present in thesaurus as well // bounce value if it is present in thesaurus as well
@@ -143,6 +160,25 @@ class TwigServiceProvider implements ServiceProviderInterface
$twig->addFilter(new \Twig_SimpleFilter('escapeDoubleQuote', function ($value) { $twig->addFilter(new \Twig_SimpleFilter('escapeDoubleQuote', function ($value) {
return str_replace('"', '\"', $value); return str_replace('"', '\"', $value);
})); }));
$twig->addFilter(new \Twig_SimpleFilter('formatDuration',
function ($secondsInDecimals) {
$time = [];
$hours = floor($secondsInDecimals / 3600);
$secondsInDecimals -= $hours * 3600;
$minutes = floor($secondsInDecimals / 60);
$secondsInDecimals -= $minutes * 60;
$seconds = intVal($secondsInDecimals % 60, 10);
if ($hours > 0) {
array_push($time, (strlen($hours) < 2) ? "0{$hours}" : $hours);
}
array_push($time, (strlen($minutes) < 2) ? "0{$minutes}" : $minutes);
array_push($time, (strlen($seconds) < 2) ? "0{$seconds}" : $seconds);
$formattedTime = implode(':', $time);
return $formattedTime;
}
));
} }
/** /**

View File

@@ -22,11 +22,19 @@ class DataboxPathExtractor
$this->appbox = $appbox; $this->appbox = $appbox;
} }
public function extractPaths() /**
* @param string $type
* @return array
*/
public function extractPaths($type = null)
{ {
$paths = []; $paths = [];
foreach ($this->appbox->get_databoxes() as $databox) { foreach ($this->appbox->get_databoxes() as $databox) {
if ($type === 'xsendfile') {
$paths[] = (string) $databox->get_sxml_structure()->path;
}
foreach ($databox->get_subdef_structure()->getSubdefGroup('video') as $subdef) { foreach ($databox->get_subdef_structure()->getSubdefGroup('video') as $subdef) {
$paths[] = $subdef->get_path(); $paths[] = $subdef->get_path();
} }

View File

@@ -4,7 +4,9 @@ namespace Alchemy\Phrasea\Databox;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\Core\Configuration\StructureTemplate;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Utilities\StringHelper;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
/** /**
@@ -72,26 +74,101 @@ class DataboxService
} }
/** /**
* @param $databaseName
* @param DataboxConnectionSettings|null $connectionSettings
* @return bool
*/
public function exists($databaseName, DataboxConnectionSettings $connectionSettings = null)
{
$connectionSettings = $connectionSettings ?: DataboxConnectionSettings::fromArray(
$this->configuration->get(['main', 'database'])
);
$factory = $this->connectionFactory;
// do not simply try to connect to the database, list
/** @var Connection $connection */
$connection = $factory([
'host' => $connectionSettings->getHost(),
'port' => $connectionSettings->getPort(),
'user' => $connectionSettings->getUser(),
'password' => $connectionSettings->getPassword(),
'dbname' => null,
]);
$ret = false;
$databaseName = strtolower($databaseName);
$sm = $connection->getSchemaManager();
$databases = $sm->listDatabases();
foreach($databases as $database) {
if(strtolower($database) == $databaseName) {
$ret = true;
break;
}
}
return $ret;
}
/**
* @param Connection $connection
* @param \SplFileInfo $template
* @return \databox
*/
public function createDataboxFromConnection($connection, $template)
{
return \databox::create($this->app, $connection, $template);
}
/**
* @param $databaseName
* @param $templateName
* @param User $owner * @param User $owner
* @param string $databaseName
* @param string $dataTemplate
* @param DataboxConnectionSettings|null $connectionSettings * @param DataboxConnectionSettings|null $connectionSettings
* @return \databox * @return \databox
* @throws \Exception_InvalidArgument
*/ */
public function createDatabox( public function createDatabox(
$databaseName, $databaseName,
$dataTemplate, $templateName,
User $owner, User $owner,
DataboxConnectionSettings $connectionSettings = null DataboxConnectionSettings $connectionSettings = null
) { ) {
$this->validateDatabaseName($databaseName); $this->validateDatabaseName($databaseName);
$dataTemplate = new \SplFileInfo($this->rootPath . '/lib/conf.d/data_templates/' . $dataTemplate . '.xml'); /** @var StructureTemplate $st */
$st = $this->app['phraseanet.structure-template'];
$template = $st->getByName($templateName);
if(is_null($template)) {
throw new \Exception_InvalidArgument(sprintf('Databox template "%s" not found.', $templateName));
}
// if no connectionSettings (host, user, ...) are provided, create dbox beside appBox
$connectionSettings = $connectionSettings ?: DataboxConnectionSettings::fromArray( $connectionSettings = $connectionSettings ?: DataboxConnectionSettings::fromArray(
$this->configuration->get(['main', 'database']) $this->configuration->get(['main', 'database'])
); );
$factory = $this->connectionFactory; $factory = $this->connectionFactory;
if(!$this->exists($databaseName, $connectionSettings)) {
// use a tmp connection to create the database
/** @var Connection $connection */
$connection = $factory([
'host' => $connectionSettings->getHost(),
'port' => $connectionSettings->getPort(),
'user' => $connectionSettings->getUser(),
'password' => $connectionSettings->getPassword(),
'dbname' => null
]);
// the schemeManager does NOT quote identifiers, we MUST do it
// see : http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/schema-manager.html
$connection->getSchemaManager()->createDatabase(StringHelper::SqlQuote($databaseName, StringHelper::SQL_IDENTIFIER));
$connection->close();
unset($connection);
}
/** @var Connection $connection */ /** @var Connection $connection */
$connection = $factory([ $connection = $factory([
'host' => $connectionSettings->getHost(), 'host' => $connectionSettings->getHost(),
@@ -103,7 +180,8 @@ class DataboxService
$connection->connect(); $connection->connect();
$databox = \databox::create($this->app, $connection, $dataTemplate); $databox = $this->createDataboxFromConnection($connection, $template);
$databox->registerAdmin($owner); $databox->registerAdmin($owner);
$connection->close(); $connection->close();

View File

@@ -10,6 +10,7 @@
namespace Alchemy\Phrasea\Filesystem; namespace Alchemy\Phrasea\Filesystem;
use Alchemy\Phrasea\Media\Subdef\Specification\PdfSpecification;
use Alchemy\Phrasea\Model\RecordInterface; use Alchemy\Phrasea\Model\RecordInterface;
use MediaAlchemyst\Specification\SpecificationInterface; use MediaAlchemyst\Specification\SpecificationInterface;
@@ -66,6 +67,20 @@ class FilesystemService
return $pathdest . $this->generateSubdefFilename($record, $subdef); return $pathdest . $this->generateSubdefFilename($record, $subdef);
} }
public function generateTemporarySubdefPathname(\record_adapter $record, \databox_subdef $subdef, $tmpDir)
{
$tmpDir = \p4string::addEndSlash($tmpDir);
$tmpDir = $tmpDir.$subdef->getSpecs()->getType()."/";
if(!is_dir($tmpDir)){
$this->filesystem->mkdir($tmpDir);
}
$filenameSufix = "_".$record->getDataboxId()."_".$this->generateSubdefFilename($record, $subdef);
return $tmpDir . hash('sha256', $filenameSufix) . $filenameSufix;
}
/** /**
* @param RecordInterface $record * @param RecordInterface $record
* @param string|\SplFileInfo $source * @param string|\SplFileInfo $source
@@ -163,6 +178,8 @@ class FilesystemService
return $this->getExtensionFromVideoCodec($spec->getVideoCodec()); return $this->getExtensionFromVideoCodec($spec->getVideoCodec());
case SpecificationInterface::TYPE_SWF: case SpecificationInterface::TYPE_SWF:
return 'swf'; return 'swf';
case PdfSpecification::TYPE_PDF:
return 'pdf';
} }
return null; return null;

View File

@@ -56,6 +56,12 @@ class GeneralFormType extends AbstractType
'attr' => ['min' => -1], 'attr' => ['min' => -1],
'constraints' => new GreaterThanOrEqual(['value' => -1]), 'constraints' => new GreaterThanOrEqual(['value' => -1]),
]); ]);
$builder->add('personalize-logo-choice', new PersonalisationLogoForm(), [
'label' => 'Design of personalization logo section',
'attr' => [
'id' => 'personalize-logo-container'
]
]);
} }
public function getName() public function getName()

View File

@@ -0,0 +1,44 @@
<?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\Form\Configuration;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class PersonalisationLogoForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('originalChoiceInput', 'choice', [
'label' => false,
'choices' => ['original' => 'original-choice-label'],
'expanded' => true,
]);
$builder->add('personaliseChoiceInput', 'choice', [
'label' => false,
'choices' => ['personalise' => 'personalise-choice-label'],
'expanded' => true,
]);
$builder->add('personalizeLogoInput', 'file', [
'label' => false,
]);
$builder->add('logoChoice', 'hidden', [
'label' => false,
]);
}
public function getName()
{
return null;
}
}

View File

@@ -712,6 +712,8 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper
$user = $this->app['repo.users']->find($usr_id); $user = $this->app['repo.users']->find($usr_id);
$this->app->getAclForUser($user)->apply_model($template, $base_ids); $this->app->getAclForUser($user)->apply_model($template, $base_ids);
$this->app['manipulator.user']->updateUser($user);
} }
return $this; return $this;

View File

@@ -91,6 +91,7 @@ class Manage extends Helper
'sbas_id' => $this->request->get('sbas_id'), 'sbas_id' => $this->request->get('sbas_id'),
'base_id' => $this->request->get('base_id'), 'base_id' => $this->request->get('base_id'),
'last_model' => $this->request->get('last_model'), 'last_model' => $this->request->get('last_model'),
'filter_guest_user' => $this->request->get('filter_guest_user') ? true : false,
'srt' => $this->request->get("srt", \User_Query::SORT_CREATIONDATE), 'srt' => $this->request->get("srt", \User_Query::SORT_CREATIONDATE),
'ord' => $this->request->get("ord", \User_Query::ORD_DESC), 'ord' => $this->request->get("ord", \User_Query::ORD_DESC),
'per_page' => $results_quantity, 'per_page' => $results_quantity,
@@ -109,6 +110,7 @@ class Manage extends Helper
->last_model_is($this->query_parms['last_model']) ->last_model_is($this->query_parms['last_model'])
->get_inactives($this->query_parms['inactives']) ->get_inactives($this->query_parms['inactives'])
->include_templates(true) ->include_templates(true)
->include_invite($this->query_parms['filter_guest_user'])
->on_bases_where_i_am($this->app->getAclForUser($this->app->getAuthenticatedUser()), [\ACL::CANADMIN]) ->on_bases_where_i_am($this->app->getAclForUser($this->app->getAuthenticatedUser()), [\ACL::CANADMIN])
->limit($offset_start, $results_quantity) ->limit($offset_start, $results_quantity)
->execute(); ->execute();

View File

@@ -0,0 +1,36 @@
<?php
namespace Alchemy\Phrasea\Media\Subdef;
use Symfony\Component\Translation\TranslatorInterface;
use Alchemy\Phrasea\Media\Subdef\Specification\PdfSpecification;
class Pdf extends Provider
{
protected $options = [];
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function getType()
{
return self::TYPE_PDF;
}
public function getDescription()
{
return $this->translator->trans('Generates a pdf file');
}
public function getMediaAlchemystSpec()
{
if (! $this->spec) {
$this->spec = new PdfSpecification();
}
return $this->spec;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Alchemy\Phrasea\Media\Subdef\Specification;
use MediaAlchemyst\Specification\AbstractSpecification;
class PdfSpecification extends AbstractSpecification
{
const TYPE_PDF = 'pdf';
public function getType()
{
return self::TYPE_PDF;
}
}

View File

@@ -22,6 +22,7 @@ interface Subdef
const TYPE_VIDEO = 'video'; const TYPE_VIDEO = 'video';
const TYPE_AUDIO = 'audio'; const TYPE_AUDIO = 'audio';
const TYPE_FLEXPAPER = 'flexpaper'; const TYPE_FLEXPAPER = 'flexpaper';
const TYPE_PDF = 'pdf';
const TYPE_UNKNOWN = 'unknown'; const TYPE_UNKNOWN = 'unknown';
/** /**

View File

@@ -19,10 +19,19 @@ use Alchemy\Phrasea\Core\Event\Record\SubDefinitionsCreationEvent;
use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent; use Alchemy\Phrasea\Core\Event\Record\SubDefinitionCreationFailedEvent;
use Alchemy\Phrasea\Core\Event\Record\RecordEvents; use Alchemy\Phrasea\Core\Event\Record\RecordEvents;
use Alchemy\Phrasea\Filesystem\FilesystemService; use Alchemy\Phrasea\Filesystem\FilesystemService;
use Alchemy\Phrasea\Media\Subdef\Specification\PdfSpecification;
use MediaAlchemyst\Alchemyst; use MediaAlchemyst\Alchemyst;
use MediaAlchemyst\Specification\Image;
use MediaAlchemyst\Specification\Video;
use MediaVorus\MediaVorus; use MediaVorus\MediaVorus;
use MediaAlchemyst\Exception\ExceptionInterface as MediaAlchemystException; use MediaAlchemyst\Exception\ExceptionInterface as MediaAlchemystException;
use Neutron\TemporaryFilesystem\Manager;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Unoconv\Exception\ExceptionInterface as UnoconvException;
use Unoconv\Exception\RuntimeException;
use Unoconv\Unoconv;
use MediaVorus\Exception\FileNotFoundException as MediaVorusFileNotFoundException;
use MediaAlchemyst\Exception\FileNotFoundException;
class SubdefGenerator class SubdefGenerator
{ {
@@ -36,6 +45,9 @@ class SubdefGenerator
*/ */
private $logger; private $logger;
private $mediavorus; private $mediavorus;
private $tmpFilePath;
private $tmpFilesystem;
private $tmpDirectory;
public function __construct(Application $app, Alchemyst $alchemyst, FilesystemService $filesystem, MediaVorus $mediavorus, LoggerInterface $logger) public function __construct(Application $app, Alchemyst $alchemyst, FilesystemService $filesystem, MediaVorus $mediavorus, LoggerInterface $logger)
{ {
@@ -44,10 +56,31 @@ class SubdefGenerator
$this->filesystem = $filesystem; $this->filesystem = $filesystem;
$this->logger = $logger; $this->logger = $logger;
$this->mediavorus = $mediavorus; $this->mediavorus = $mediavorus;
$this->tmpDirectory = $this->app['conf']->get(['main', 'storage', 'tmp_files']);;
} }
public function generateSubdefs(\record_adapter $record, array $wanted_subdefs = null) public function generateSubdefs(\record_adapter $record, array $wanted_subdefs = null)
{ {
if ($record->get_hd_file() !== null) {
$mediaSource = $this->mediavorus->guess($record->get_hd_file()->getPathname());
$metadatas = $mediaSource->getMetadatas();
if ($metadatas->containsKey('XMP-xmp:PageImage')) {
if(!isset($this->tmpFilesystem)){
$this->tmpFilesystem = Manager::create();
}
$tmpDir = $this->tmpFilesystem->createTemporaryDirectory();
try {
$this->app['filesystem']->dumpFile($tmpDir.'/file.jpg', $metadatas->get('XMP-xmp:PageImage')->getValue()->asString());
$this->tmpFilePath = $tmpDir.'/file.jpg';
} catch (\Exception $e) {
$this->logger->error(sprintf('Unable to write temporary file : %s', $e->getMessage()));
}
}
}
if (null === $subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType())) { if (null === $subdefs = $record->getDatabox()->get_subdef_structure()->getSubdefGroup($record->getType())) {
$this->logger->info(sprintf('Nothing to do for %s', $record->getType())); $this->logger->info(sprintf('Nothing to do for %s', $record->getType()));
$subdefs = []; $subdefs = [];
@@ -118,6 +151,13 @@ class SubdefGenerator
$record->clearSubdefCache($subdefname); $record->clearSubdefCache($subdefname);
} }
if(isset($this->tmpFilesystem)){
$this->tmpFilesystem->clean();
}
if(isset($this->tmpFilePath)){
unset($this->tmpFilePath);
}
$this->dispatch( $this->dispatch(
RecordEvents::SUB_DEFINITIONS_CREATED, RecordEvents::SUB_DEFINITIONS_CREATED,
new SubDefinitionsCreatedEvent( new SubDefinitionsCreatedEvent(
@@ -136,9 +176,59 @@ class SubdefGenerator
return; return;
} }
$this->alchemyst->turnInto($record->get_hd_file()->getPathname(), $pathdest, $subdef_class->getSpecs()); $destFile = null;
if(!empty($this->tmpDirectory)){
$destFile = $pathdest;
$pathdest = $this->filesystem->generateTemporarySubdefPathname($record, $subdef_class, $this->tmpDirectory);
}
if (isset($this->tmpFilePath) && $subdef_class->getSpecs() instanceof Image) {
$this->alchemyst->turnInto($this->tmpFilePath, $pathdest, $subdef_class->getSpecs());
} elseif ($subdef_class->getSpecs() instanceof PdfSpecification){
$this->generatePdfSubdef($record->get_hd_file()->getPathname(), $pathdest);
} else {
$this->alchemyst->turnInto($record->get_hd_file()->getPathname(), $pathdest, $subdef_class->getSpecs());
}
if($destFile){
$this->filesystem->copy($pathdest, $destFile);
$this->app['filesystem']->remove($pathdest);
}
} catch (MediaAlchemystException $e) { } catch (MediaAlchemystException $e) {
$this->logger->error(sprintf('Subdef generation failed for record %d with message %s', $record->getRecordId(), $e->getMessage())); $this->logger->error(sprintf('Subdef generation failed for record %d with message %s', $record->getRecordId(), $e->getMessage()));
} }
} }
private function generatePdfSubdef($source, $pathdest)
{
try {
$mediafile = $this->app['mediavorus']->guess($source);
} catch (MediaVorusFileNotFoundException $e) {
throw new FileNotFoundException(sprintf('File %s not found', $source));
}
try {
if ($mediafile->getFile()->getMimeType() != 'application/pdf') {
$this->app['unoconv']->transcode(
$mediafile->getFile()->getPathname(), Unoconv::FORMAT_PDF, $pathdest
);
} else {
copy($mediafile->getFile()->getPathname(), $pathdest);
}
} catch (UnoconvException $e) {
throw new RuntimeException('Unable to transmute document to pdf due to Unoconv', null, $e);
} catch (\Exception $e) {
throw $e;
}
}
} }

View File

@@ -11,6 +11,7 @@
namespace Alchemy\Phrasea\Metadata; namespace Alchemy\Phrasea\Metadata;
use Alchemy\Phrasea\Border\File;
use Alchemy\Phrasea\Databox\DataboxRepository; use Alchemy\Phrasea\Databox\DataboxRepository;
use Alchemy\Phrasea\Metadata\Tag\NoSource; use Alchemy\Phrasea\Metadata\Tag\NoSource;
use PHPExiftool\Driver\Metadata\Metadata; use PHPExiftool\Driver\Metadata\Metadata;
@@ -119,8 +120,11 @@ class PhraseanetMetadataSetter
if (!isset($metadataPerField[$fieldName])) { if (!isset($metadataPerField[$fieldName])) {
$metadataPerField[$fieldName] = []; $metadataPerField[$fieldName] = [];
} }
if(in_array($tagName, File::$xmpTag)){
$metadataPerField[$fieldName] = array_merge($metadataPerField[$fieldName], $metadata->getValue()->asArray()); $metadataPerField[$fieldName] = array_merge($metadataPerField[$fieldName], (array) File::sanitizeXmpUuid($metadata->getValue()->asString()));
}else{
$metadataPerField[$fieldName] = array_merge($metadataPerField[$fieldName], $metadata->getValue()->asArray());
}
} }
} }

View File

@@ -53,6 +53,42 @@ class ElasticsearchRecord implements RecordInterface, MutableRecordInterface
private $flags = []; private $flags = [];
private $highlight = []; private $highlight = [];
public function asArray()
{
return [
'_index' => $this->_index,
'_type' => $this->_type,
'_id' => $this->_id,
'_version' => $this->_version,
'_score' => $this->_score,
'databoxId' => $this->databoxId,
'recordId' => $this->recordId,
'collectionId' => $this->collectionId,
'baseId' => $this->baseId,
'collectionName' => $this->collectionName,
'mimeType' => $this->mimeType,
'title' => $this->title,
'originalName' => $this->originalName,
'updated' => $this->updated,
'created' => $this->created,
'sha256' => $this->sha256,
'width' => $this->width,
'height' => $this->height,
'size' => $this->size,
'uuid' => $this->uuid,
'position' => $this->position,
'type' => $this->type,
'status' => $this->status,
'isStory' => $this->isStory,
'caption' => $this->caption,
'privateCaption' => $this->privateCaption,
'exif' => $this->exif,
'subdefs' => $this->subdefs,
'flags' => $this->flags,
'highlight' => $this->highlight,
];
}
/** /**
* @param string $index * @param string $index
* @param string $type * @param string $type

View File

@@ -386,6 +386,17 @@ class LazaretFile
$this->checks->removeElement($checks); $this->checks->removeElement($checks);
} }
/**
* @param LazaretCheck $checks
* @return string
*/
public function getCheckerName(LazaretCheck $checks)
{
$checkNameTab = explode('\\', $checks->getCheckClassname());
return $checkNameTab[4];
}
/** /**
* Get checks * Get checks
* *
@@ -396,6 +407,19 @@ class LazaretFile
return $this->checks; return $this->checks;
} }
/**
* @return array $checkers
*/
public function getChecksWhithNameKey()
{
$checkers = [];
foreach($this->checks as $check){
$checkers[$this->getCheckerName($check)] = $check;
}
return $checkers;
}
/** /**
* Set session * Set session
* *
@@ -439,7 +463,7 @@ class LazaretFile
'reasons' => [] 'reasons' => []
]; ];
} }
$merged[$record->getRecordId()]['reasons'][] = $check->getReason($app['translator']); $merged[$record->getRecordId()]['reasons'][$this->getCheckerName($check)] = $check->getReason($app['translator']);
} }
else { else {
$merged[$record->getRecordId()] = $record; $merged[$record->getRecordId()] = $record;

View File

@@ -390,4 +390,9 @@ class UserManipulator implements ManipulatorInterface
return $var; return $var;
} }
public function updateUser(User $user)
{
$this->manager->update($user);
}
} }

View File

@@ -179,7 +179,15 @@ class ApiOrderController extends BaseOrderController
set_time_limit(0); set_time_limit(0);
ignore_user_abort(true); ignore_user_abort(true);
$file = \set_export::build_zip($this->app, $token, $exportData, $token->getValue() . '.zip');
$tmpDownloadDir = \p4string::addEndSlash($this->app['tmp.download.path']);
if(is_null($tmpDownloadDir)){
$tmpDownloadDir = '';
}
$zipFile = $tmpDownloadDir.'order_'.$token->getValue() . '.zip';
$file = \set_export::build_zip($this->app, $token, $exportData, $zipFile);
return $this->deliverFile($file, $exportName, DeliverDataInterface::DISPOSITION_INLINE, 'application/zip'); return $this->deliverFile($file, $exportName, DeliverDataInterface::DISPOSITION_INLINE, 'application/zip');
} }

View File

@@ -25,6 +25,7 @@ class PDF
const LAYOUT_PREVIEWCAPTIONTDM = 'previewCaptionTdm'; const LAYOUT_PREVIEWCAPTIONTDM = 'previewCaptionTdm';
const LAYOUT_THUMBNAILLIST = 'thumbnailList'; const LAYOUT_THUMBNAILLIST = 'thumbnailList';
const LAYOUT_THUMBNAILGRID = 'thumbnailGrid'; const LAYOUT_THUMBNAILGRID = 'thumbnailGrid';
const LAYOUT_CAPTION = 'caption';
public function __construct(Application $app, array $records, $layout) public function __construct(Application $app, array $records, $layout)
{ {
@@ -73,6 +74,8 @@ class PDF
continue 2; continue 2;
} }
break; break;
case self::LAYOUT_CAPTION:
break;
} }
$record->setNumber(count($list) + 1); $record->setNumber(count($list) + 1);
@@ -106,6 +109,9 @@ class PDF
case self::LAYOUT_THUMBNAILGRID: case self::LAYOUT_THUMBNAILGRID:
$this->print_thumbnailGrid(); $this->print_thumbnailGrid();
break; break;
case self::LAYOUT_CAPTION:
$this->print_caption();
break;
} }
return $this; return $this;
@@ -315,6 +321,67 @@ class PDF
$this->pdf->SetLeftMargin($lmargin); $this->pdf->SetLeftMargin($lmargin);
} }
protected function print_caption()
{
$this->pdf->AddPage();
$oldMargins = $this->pdf->getMargins();
$lmargin = $oldMargins['left'];
$rmargin = $oldMargins['right'];
foreach ($this->records as $rec) {
$title = "record : " . $rec->get_title();
$y = $this->pdf->GetY();
if($this->pdf->getPageHeight() - $y < 20){ // height of the footer is 15
$this->pdf->AddPage();
$y = $oldMargins['top'];
}
$t = \phrasea::bas_labels($rec->getBaseId(), $this->app);
$this->pdf->SetFont(PhraseaPDF::FONT, '', 10);
$this->pdf->SetFillColor(220, 220, 220);
$this->pdf->SetLeftMargin($lmargin);
$this->pdf->SetRightMargin($rmargin);
$this->pdf->SetX($lmargin);
$this->pdf->SetY($y);
$this->pdf->out = false;
$this->pdf->MultiCell(140, 4, $title, "LTR", "L", 1);
$y2 = $this->pdf->GetY();
$h = $y2 - $y;
$this->pdf->out = true;
$this->pdf->SetX($lmargin);
$this->pdf->SetY($y);
$this->pdf->Cell(0, $h, "", "LTR", 1, "R", 1);
$this->pdf->SetX($lmargin);
$this->pdf->SetY($y);
$this->pdf->Cell(0, 4, $t, "", 1, "R");
$this->pdf->SetX($lmargin);
$this->pdf->SetY($y);
$this->pdf->MultiCell(140, 4, $title, "", "L");
$this->pdf->SetX($lmargin);
$this->pdf->SetY($y = $y2);
$this->pdf->SetY($y + 2);
foreach ($rec->get_caption()->get_fields() as $field) {
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $field->get_name() . " : ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$t = str_replace(
["&lt;", "&gt;", "&amp;"]
, ["<", ">", "&"]
, strip_tags($field->get_serialized_values())
);
$this->pdf->Write(5, $t);
$this->pdf->Write(6, "\n");
}
$this->pdf->SetY($this->pdf->GetY() + 10);
}
}
protected function print_preview($withtdm, $write_caption) protected function print_preview($withtdm, $write_caption)
{ {
if ($withtdm === true) { if ($withtdm === true) {

View File

@@ -26,4 +26,4 @@ class V1SearchRecordsResultTransformer extends V1SearchTransformer
{ {
return $this->collection($resultView->getRecords(), $this->recordTransformer); return $this->collection($resultView->getRecords(), $this->recordTransformer);
} }
} }

View File

@@ -36,7 +36,7 @@ abstract class V1SearchTransformer extends TransformerAbstract
return $suggestion->toArray(); return $suggestion->toArray();
}, $result->getSuggestions()->toArray()), }, $result->getSuggestions()->toArray()),
'facets' => $result->getFacets(), 'facets' => $result->getFacets(),
'query' => $result->getEngineQuery(), 'query' => $result->getQueryText(),
]; ];
} }

View File

@@ -27,11 +27,16 @@ class EqualExpression extends Node
throw new QueryException(sprintf('Value "%s" for key "%s" is not valid.', $this->value, $this->key)); throw new QueryException(sprintf('Value "%s" for key "%s" is not valid.', $this->value, $this->key));
} }
$query = [ if(method_exists($this->key, "buildQuery")) {
'term' => [ $query = $this->key->buildQuery($this->value, $context);
$this->key->getIndexField($context, true) => $this->value }
] else {
]; $query = [
'term' => [
$this->key->getIndexField($context, true) => $this->value
]
];
}
if ($this->key instanceof QueryPostProcessor) { if ($this->key instanceof QueryPostProcessor) {
return $this->key->postProcessQuery($query, $context); return $this->key->postProcessQuery($query, $context);

View File

@@ -0,0 +1,75 @@
<?php
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
class GeolocationKey implements Key
{
const TYPE_GEOLOCATION = 'geolocation';
private $type;
private $key;
private $matcher = '/^(-?\\d*(\\.\\d*)?)[\\s]+(-?\\d*(\\.\\d*)?)[\\s]+(\\d*(\\.\\d*)?)(\\D*)$/';
private $units = [
'mi', 'miles',
'yd', 'yards',
'ft', 'feet',
'in', 'inch',
'km', 'kilometers',
'm', 'meters',
'cm', 'centimeters',
'mm', 'millimeters',
'NM', 'nmi', 'nauticalmiles',
];
public static function geolocation()
{
return new self(self::TYPE_GEOLOCATION, 'geolocation');
}
public function buildQuery($value, QueryContext $context)
{
$matches = [];
if(preg_match($this->matcher, trim($value), $matches) === 1) {
$lat = $matches[1];
$lon = $matches[3];
$dst = $matches[5];
$uni = trim($matches[7]);
if(in_array($uni, $this->units)) {
return [
'geo_distance' => [
'distance' => $dst . $uni,
'location' => [
'lat' => $lat,
'lon' => $lon
]
]
];
}
}
return null;
}
private function __construct($type, $key)
{
$this->type = $type;
$this->key = $key;
}
public function getIndexField(QueryContext $context, $raw = false)
{
return $this->key;
}
public function isValueCompatible($value, QueryContext $context)
{
return preg_match($this->matcher, trim($value), $matches) === 1;
}
public function __toString()
{
return $this->type;
}
}

View File

@@ -8,6 +8,8 @@ class NativeKey implements Key
{ {
const TYPE_DATABASE = 'database'; const TYPE_DATABASE = 'database';
const TYPE_COLLECTION = 'collection'; const TYPE_COLLECTION = 'collection';
const TYPE_SHA256 = 'sha256';
const TYPE_UUID = 'uuid';
const TYPE_MEDIA_TYPE = 'media_type'; const TYPE_MEDIA_TYPE = 'media_type';
const TYPE_RECORD_IDENTIFIER = 'record_identifier'; const TYPE_RECORD_IDENTIFIER = 'record_identifier';
@@ -24,6 +26,16 @@ class NativeKey implements Key
return new self(self::TYPE_COLLECTION, 'collection_name'); return new self(self::TYPE_COLLECTION, 'collection_name');
} }
public static function sha256()
{
return new self(self::TYPE_SHA256, 'sha256');
}
public static function uuid()
{
return new self(self::TYPE_UUID, 'uuid');
}
public static function mediaType() public static function mediaType()
{ {
return new self(self::TYPE_MEDIA_TYPE, 'type'); return new self(self::TYPE_MEDIA_TYPE, 'type');

View File

@@ -35,16 +35,21 @@ class DataboxFetcherFactory
*/ */
private $recordHelper; private $recordHelper;
/** @var ElasticsearchOptions */
private $options;
/** /**
* @param RecordHelper $recordHelper * @param RecordHelper $recordHelper
* @param ElasticsearchOptions $options
* @param \ArrayAccess $container * @param \ArrayAccess $container
* @param string $structureKey * @param string $structureKey
* @param string $thesaurusKey * @param string $thesaurusKey
*/ */
public function __construct(RecordHelper $recordHelper, \ArrayAccess $container, $structureKey, $thesaurusKey) public function __construct(RecordHelper $recordHelper, ElasticsearchOptions $options, \ArrayAccess $container, $structureKey, $thesaurusKey)
{ {
$this->recordHelper = $recordHelper; $this->recordHelper = $recordHelper;
$this->container = $container; $this->options = $options;
$this->container = $container;
$this->structureKey = $structureKey; $this->structureKey = $structureKey;
$this->thesaurusKey = $thesaurusKey; $this->thesaurusKey = $thesaurusKey;
} }
@@ -59,14 +64,19 @@ class DataboxFetcherFactory
$connection = $databox->get_connection(); $connection = $databox->get_connection();
$candidateTerms = new CandidateTerms($databox); $candidateTerms = new CandidateTerms($databox);
$fetcher = new Fetcher($databox, array( $fetcher = new Fetcher(
new CoreHydrator($databox->get_sbas_id(), $databox->get_viewname(), $this->recordHelper), $databox,
new TitleHydrator($connection), $this->options,
new MetadataHydrator($connection, $this->getStructure(), $this->recordHelper), [
new FlagHydrator($this->getStructure(), $databox), new CoreHydrator($databox->get_sbas_id(), $databox->get_viewname(), $this->recordHelper),
new ThesaurusHydrator($this->getStructure(), $this->getThesaurus(), $candidateTerms), new TitleHydrator($connection, $this->recordHelper),
new SubDefinitionHydrator($connection) new MetadataHydrator($connection, $this->getStructure(), $this->recordHelper),
), $fetcherDelegate); new FlagHydrator($this->getStructure(), $databox),
new ThesaurusHydrator($this->getStructure(), $this->getThesaurus(), $candidateTerms),
new SubDefinitionHydrator($connection)
],
$fetcherDelegate
);
$fetcher->setBatchSize(200); $fetcher->setBatchSize(200);
$fetcher->onDrain(function() use ($candidateTerms) { $fetcher->onDrain(function() use ($candidateTerms) {

View File

@@ -139,6 +139,7 @@ class ElasticSearchEngine implements SearchEngineInterface
return [ return [
SearchEngineOptions::SORT_RELEVANCE => $this->app->trans('pertinence'), SearchEngineOptions::SORT_RELEVANCE => $this->app->trans('pertinence'),
SearchEngineOptions::SORT_CREATED_ON => $this->app->trans('date dajout'), SearchEngineOptions::SORT_CREATED_ON => $this->app->trans('date dajout'),
SearchEngineOptions::SORT_UPDATED_ON => $this->app->trans('date de modification'),
]; ];
} }
@@ -201,6 +202,11 @@ class ElasticSearchEngine implements SearchEngineInterface
$this->notImplemented(); $this->notImplemented();
} }
private function notImplemented()
{
throw new LogicException('Not implemented');
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@@ -241,11 +247,6 @@ class ElasticSearchEngine implements SearchEngineInterface
$this->notImplemented(); $this->notImplemented();
} }
private function notImplemented()
{
throw new LogicException('Not implemented');
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@@ -273,35 +274,35 @@ class ElasticSearchEngine implements SearchEngineInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function query($string, SearchEngineOptions $options = null) public function query($queryText, SearchEngineOptions $options = null)
{ {
$options = $options ?: new SearchEngineOptions(); $options = $options ?: new SearchEngineOptions();
$context = $this->context_factory->createContext($options); $context = $this->context_factory->createContext($options);
/** @var QueryCompiler $query_compiler */ /** @var QueryCompiler $query_compiler */
$query_compiler = $this->app['query_compiler']; $query_compiler = $this->app['query_compiler'];
$recordQuery = $query_compiler->compile($string, $context); $queryAST = $query_compiler->parse($queryText)->dump();
$queryCompiled = $query_compiler->compile($queryText, $context);
$params = $this->createRecordQueryParams($recordQuery, $options, null); $queryESLib = $this->createRecordQueryParams($queryCompiled, $options, null);
// ask ES to return field _version (incremental version number of document) // ask ES to return field _version (incremental version number of document)
$params['body']['version'] = true; $queryESLib['body']['version'] = true;
$params['body']['from'] = $options->getFirstResult(); $queryESLib['body']['from'] = $options->getFirstResult();
$params['body']['size'] = $options->getMaxResults(); $queryESLib['body']['size'] = $options->getMaxResults();
if($this->options->getHighlight()) { if($this->options->getHighlight()) {
$params['body']['highlight'] = $this->buildHighlightRules($context); $queryESLib['body']['highlight'] = $this->buildHighlightRules($context);
} }
$aggs = $this->getAggregationQueryParams($options); $aggs = $this->getAggregationQueryParams($options);
if ($aggs) { if ($aggs) {
$params['body']['aggs'] = $aggs; $queryESLib['body']['aggs'] = $aggs;
} }
$res = $this->client->search($params); $res = $this->client->search($queryESLib);
$results = new ArrayCollection(); $results = new ArrayCollection();
$n = 0; $n = 0;
foreach ($res['hits']['hits'] as $hit) { foreach ($res['hits']['hits'] as $hit) {
$results[] = ElasticsearchRecordHydrator::hydrate($hit, $n++); $results[] = ElasticsearchRecordHydrator::hydrate($hit, $n++);
@@ -310,16 +311,13 @@ class ElasticSearchEngine implements SearchEngineInterface
/** @var FacetsResponse $facets */ /** @var FacetsResponse $facets */
$facets = $this->facetsResponseFactory->__invoke($res); $facets = $this->facetsResponseFactory->__invoke($res);
$query['ast'] = $query_compiler->parse($string)->dump();
$query['query_main'] = $recordQuery;
$query['query'] = $params['body'];
$query['query_string'] = json_encode($params['body'], JSON_PRETTY_PRINT);
return new SearchEngineResult( return new SearchEngineResult(
$options, $options,
$results, // ArrayCollection of results $results, // ArrayCollection of results
$string, // the query as typed by the user $queryText, // the query as typed by the user
json_encode($query), $queryAST,
$queryCompiled,
$queryESLib,
$res['took'], // duration $res['took'], // duration
$options->getFirstResult(), $options->getFirstResult(),
$res['hits']['total'], // available $res['hits']['total'], // available
@@ -333,131 +331,6 @@ class ElasticSearchEngine implements SearchEngineInterface
); );
} }
private function buildHighlightRules(QueryContext $context)
{
$highlighted_fields = [];
foreach ($context->getHighlightedFields() as $field) {
switch ($field->getType()) {
case FieldMapping::TYPE_STRING:
$index_field = $field->getIndexField();
$raw_index_field = $field->getIndexField(true);
$highlighted_fields[$index_field] = [
// Requires calling Mapping::enableTermVectors() on this field mapping
'matched_fields' => [$index_field, $raw_index_field],
'type' => 'fvh'
];
break;
case FieldMapping::TYPE_FLOAT:
case FieldMapping::TYPE_DOUBLE:
case FieldMapping::TYPE_INTEGER:
case FieldMapping::TYPE_LONG:
case FieldMapping::TYPE_SHORT:
case FieldMapping::TYPE_BYTE:
continue;
case FieldMapping::TYPE_DATE:
default:
continue;
}
}
return [
'pre_tags' => ['[[em]]'],
'post_tags' => ['[[/em]]'],
'order' => 'score',
'fields' => $highlighted_fields
];
}
/**
* {@inheritdoc}
*/
public function autocomplete($query, SearchEngineOptions $options)
{
$params = $this->createCompletionParams($query, $options);
$res = $this->client->suggest($params);
$ret = [
'text' => [],
'byField' => []
];
foreach(array_keys($params['body']) as $fname) {
$t = [];
foreach($res[$fname] as $suggest) { // don't know why there is a sub-array level
foreach($suggest['options'] as $option) {
$text = $option['text'];
if(!in_array($text, $ret['text'])) {
$ret['text'][] = $text;
}
$t[] = [
'label' => $text,
'query' => $fname.':'.$text
];
}
}
if(!empty($t)) {
$ret['byField'][$fname] = $t;
}
}
return $ret;
}
/**
* {@inheritdoc}
*/
public function resetCache()
{
}
/**
* {@inheritdoc}
*/
public function clearCache()
{
}
/**
* {@inheritdoc}
*/
public function clearAllCache(\DateTime $date = null)
{
}
private function createCompletionParams($query, SearchEngineOptions $options)
{
$body = [];
$context = [
'record_type' => $options->getSearchType() === SearchEngineOptions::RECORD_RECORD ?
SearchEngineInterface::GEM_TYPE_RECORD : SearchEngineInterface::GEM_TYPE_STORY
];
$base_ids = $options->getBasesIds();
if (count($base_ids) > 0) {
$context['base_id'] = $base_ids;
}
$search_context = $this->context_factory->createContext($options);
$fields = $search_context->getUnrestrictedFields();
foreach($fields as $field) {
if($field->getType() == FieldMapping::TYPE_STRING) {
$k = '' . $field->getName();
$body[$k] = [
'text' => $query,
'completion' => [
'field' => "caption." . $field->getName() . ".suggest",
'context' => &$context
]
];
}
}
return [
'index' => $this->indexName,
'body' => $body
];
}
private function createRecordQueryParams($ESQuery, SearchEngineOptions $options, \record_adapter $record = null) private function createRecordQueryParams($ESQuery, SearchEngineOptions $options, \record_adapter $record = null)
{ {
$params = [ $params = [
@@ -486,66 +359,31 @@ class ElasticSearchEngine implements SearchEngineInterface
return $params; return $params;
} }
private function getAggregationQueryParams(SearchEngineOptions $options) private function createSortQueryParams(SearchEngineOptions $options)
{ {
$aggs = []; $sort = [];
// technical aggregates (enable + optional limit)
foreach (ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
$size = $this->options->getAggregableFieldLimit($k);
if ($size !== databox_field::FACET_DISABLED) {
if ($size === databox_field::FACET_NO_LIMIT) {
$size = ESField::FACET_NO_LIMIT;
}
$agg = [
'terms' => [
'field' => $f['field'],
'size' => $size
]
];
$aggs[$k] = $agg;
}
}
// fields aggregates
$structure = $this->context_factory->getLimitedStructure($options);
foreach ($structure->getFacetFields() as $name => $field) {
// 2015-05-26 (mdarse) Removed databox filtering.
// It was already done by the ACL filter in the query scope, so no
// document that shouldn't be displayed can go this far.
$agg = [
'terms' => [
'field' => $field->getIndexField(true),
'size' => $field->getFacetValuesLimit()
]
];
$aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg);
}
return $aggs;
}
private function createACLFilters(SearchEngineOptions $options) if ($options->getSortBy() === null || $options->getSortBy() === SearchEngineOptions::SORT_RELEVANCE) {
{ $sort['_score'] = $options->getSortOrder();
// No ACLs if no user }
if (false === $this->app->getAuthenticator()->isAuthenticated()) { elseif ($options->getSortBy() === SearchEngineOptions::SORT_CREATED_ON) {
return []; $sort['created_on'] = $options->getSortOrder();
}
elseif ($options->getSortBy() === SearchEngineOptions::SORT_UPDATED_ON) {
$sort['updated_on'] = $options->getSortOrder();
}
elseif ($options->getSortBy() === 'recordid') {
$sort['record_id'] = $options->getSortOrder();
}
else {
$sort[sprintf('caption.%s', $options->getSortBy())] = $options->getSortOrder();
} }
$acl = $this->app->getAclForUser($this->app->getAuthenticatedUser()); if (!array_key_exists('record_id', $sort)) {
$sort['record_id'] = $options->getSortOrder();
$grantedCollections = array_keys($acl->get_granted_base([\ACL::ACTIF]));
if (count($grantedCollections) === 0) {
return ['bool' => ['must_not' => ['match_all' => new \stdClass()]]];
} }
$appbox = $this->app['phraseanet.appbox']; return $sort;
$flagNamesMap = $this->getFlagsKey($appbox);
// Get flags rules
$flagRules = $this->getFlagsRules($appbox, $acl, $grantedCollections);
// Get intersection between collection ACLs and collection chosen by end user
$aclRules = $this->getACLsByCollection($flagRules, $flagNamesMap);
return $this->buildACLsFilters($aclRules, $options);
} }
private function createQueryFilters(SearchEngineOptions $options) private function createQueryFilters(SearchEngineOptions $options)
@@ -603,27 +441,6 @@ class ElasticSearchEngine implements SearchEngineInterface
return $filters; return $filters;
} }
private function createSortQueryParams(SearchEngineOptions $options)
{
$sort = [];
if ($options->getSortBy() === null || $options->getSortBy() === SearchEngineOptions::SORT_RELEVANCE) {
$sort['_score'] = $options->getSortOrder();
} elseif ($options->getSortBy() === SearchEngineOptions::SORT_CREATED_ON) {
$sort['created_on'] = $options->getSortOrder();
} elseif ($options->getSortBy() === 'recordid') {
$sort['record_id'] = $options->getSortOrder();
} else {
$sort[sprintf('caption.%s', $options->getSortBy())] = $options->getSortOrder();
}
if (! array_key_exists('record_id', $sort)) {
$sort['record_id'] = $options->getSortOrder();
}
return $sort;
}
private function getFlagsKey(\appbox $appbox) private function getFlagsKey(\appbox $appbox)
{ {
$flags = []; $flags = [];
@@ -638,6 +455,32 @@ class ElasticSearchEngine implements SearchEngineInterface
return $flags; return $flags;
} }
private function createACLFilters(SearchEngineOptions $options)
{
// No ACLs if no user
if (false === $this->app->getAuthenticator()->isAuthenticated()) {
return [];
}
$acl = $this->app->getAclForUser($this->app->getAuthenticatedUser());
$grantedCollections = array_keys($acl->get_granted_base([\ACL::ACTIF]));
if (count($grantedCollections) === 0) {
return ['bool' => ['must_not' => ['match_all' => new \stdClass()]]];
}
$appbox = $this->app['phraseanet.appbox'];
$flagNamesMap = $this->getFlagsKey($appbox);
// Get flags rules
$flagRules = $this->getFlagsRules($appbox, $acl, $grantedCollections);
// Get intersection between collection ACLs and collection chosen by end user
$aclRules = $this->getACLsByCollection($flagRules, $flagNamesMap);
return $this->buildACLsFilters($aclRules, $options);
}
private function getFlagsRules(\appbox $appbox, \ACL $acl, array $collections) private function getFlagsRules(\appbox $appbox, \ACL $acl, array $collections)
{ {
$rules = []; $rules = [];
@@ -769,4 +612,166 @@ class ElasticSearchEngine implements SearchEngineInterface
return []; return [];
} }
} }
private function buildHighlightRules(QueryContext $context)
{
$highlighted_fields = [];
foreach ($context->getHighlightedFields() as $field) {
switch ($field->getType()) {
case FieldMapping::TYPE_STRING:
$index_field = $field->getIndexField();
$raw_index_field = $field->getIndexField(true);
$highlighted_fields[$index_field . ".light"] = [
// Requires calling Mapping::enableTermVectors() on this field mapping
// 'matched_fields' => [$index_field, $raw_index_field],
'type' => 'fvh',
];
break;
case FieldMapping::TYPE_FLOAT:
case FieldMapping::TYPE_DOUBLE:
case FieldMapping::TYPE_INTEGER:
case FieldMapping::TYPE_LONG:
case FieldMapping::TYPE_SHORT:
case FieldMapping::TYPE_BYTE:
continue;
case FieldMapping::TYPE_DATE:
default:
continue;
}
}
return [
'pre_tags' => ['[[em]]'],
'post_tags' => ['[[/em]]'],
'order' => 'score',
'fields' => $highlighted_fields
];
}
private function getAggregationQueryParams(SearchEngineOptions $options)
{
$aggs = [];
// technical aggregates (enable + optional limit)
foreach (ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
$size = $this->options->getAggregableFieldLimit($k);
if ($size !== databox_field::FACET_DISABLED) {
if ($size === databox_field::FACET_NO_LIMIT) {
$size = ESField::FACET_NO_LIMIT;
}
$agg = [
'terms' => [
'field' => $f['field'],
'size' => $size
]
];
$aggs[$k] = $agg;
}
}
// fields aggregates
$structure = $this->context_factory->getLimitedStructure($options);
foreach ($structure->getFacetFields() as $name => $field) {
// 2015-05-26 (mdarse) Removed databox filtering.
// It was already done by the ACL filter in the query scope, so no
// document that shouldn't be displayed can go this far.
$agg = [
'terms' => [
'field' => $field->getIndexField(true),
'size' => $field->getFacetValuesLimit()
]
];
$aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg);
}
return $aggs;
}
/**
* {@inheritdoc}
*/
public function autocomplete($query, SearchEngineOptions $options)
{
$params = $this->createCompletionParams($query, $options);
$res = $this->client->suggest($params);
$ret = [
'text' => [],
'byField' => []
];
foreach (array_keys($params['body']) as $fname) {
$t = [];
foreach ($res[$fname] as $suggest) { // don't know why there is a sub-array level
foreach ($suggest['options'] as $option) {
$text = $option['text'];
if (!in_array($text, $ret['text'])) {
$ret['text'][] = $text;
}
$t[] = [
'label' => $text,
'query' => $fname . ':' . $text
];
}
}
if (!empty($t)) {
$ret['byField'][$fname] = $t;
}
}
return $ret;
}
private function createCompletionParams($query, SearchEngineOptions $options)
{
$body = [];
$context = [
'record_type' => $options->getSearchType() === SearchEngineOptions::RECORD_RECORD ?
SearchEngineInterface::GEM_TYPE_RECORD : SearchEngineInterface::GEM_TYPE_STORY
];
$base_ids = $options->getBasesIds();
if (count($base_ids) > 0) {
$context['base_id'] = $base_ids;
}
$search_context = $this->context_factory->createContext($options);
$fields = $search_context->getUnrestrictedFields();
foreach ($fields as $field) {
if ($field->getType() == FieldMapping::TYPE_STRING) {
$k = '' . $field->getName();
$body[$k] = [
'text' => $query,
'completion' => [
'field' => "caption." . $field->getName() . ".suggest",
'context' => &$context
]
];
}
}
return [
'index' => $this->indexName,
'body' => $body
];
}
/**
* {@inheritdoc}
*/
public function resetCache()
{
}
/**
* {@inheritdoc}
*/
public function clearCache()
{
}
/**
* {@inheritdoc}
*/
public function clearAllCache(\DateTime $date = null)
{
}
} }

View File

@@ -11,6 +11,10 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic;
class ElasticsearchOptions class ElasticsearchOptions
{ {
const POPULATE_ORDER_RID = "RECORD_ID";
const POPULATE_ORDER_MODDATE = "MODIFICATION_DATE";
const POPULATE_DIRECTION_ASC = "ASC";
const POPULATE_DIRECTION_DESC = "DESC";
/** @var string */ /** @var string */
private $host; private $host;
/** @var int */ /** @var int */
@@ -25,6 +29,10 @@ class ElasticsearchOptions
private $minScore; private $minScore;
/** @var bool */ /** @var bool */
private $highlight; private $highlight;
/** @var string */
private $populateOrder;
/** @var string */
private $populateDirection;
/** @var int[] */ /** @var int[] */
private $_customValues; private $_customValues;
@@ -46,6 +54,8 @@ class ElasticsearchOptions
'replicas' => 0, 'replicas' => 0,
'minScore' => 4, 'minScore' => 4,
'highlight' => true, 'highlight' => true,
'populate_order' => self::POPULATE_ORDER_RID,
'populate_direction' => self::POPULATE_DIRECTION_DESC,
'activeTab' => null, 'activeTab' => null,
]; ];
@@ -63,6 +73,8 @@ class ElasticsearchOptions
$self->setReplicas($options['replicas']); $self->setReplicas($options['replicas']);
$self->setMinScore($options['minScore']); $self->setMinScore($options['minScore']);
$self->setHighlight($options['highlight']); $self->setHighlight($options['highlight']);
$self->setPopulateOrder($options['populate_order']);
$self->setPopulateDirection($options['populate_direction']);
$self->setActiveTab($options['activeTab']); $self->setActiveTab($options['activeTab']);
foreach(self::getAggregableTechnicalFields() as $k => $f) { foreach(self::getAggregableTechnicalFields() as $k => $f) {
$self->setAggregableFieldLimit($k, $options[$k.'_limit']); $self->setAggregableFieldLimit($k, $options[$k.'_limit']);
@@ -85,6 +97,8 @@ class ElasticsearchOptions
'replicas' => $this->replicas, 'replicas' => $this->replicas,
'minScore' => $this->minScore, 'minScore' => $this->minScore,
'highlight' => $this->highlight, 'highlight' => $this->highlight,
'populate_order' => $this->populateOrder,
'populate_direction' => $this->populateDirection,
'activeTab' => $this->activeTab 'activeTab' => $this->activeTab
]; ];
foreach(self::getAggregableTechnicalFields() as $k => $f) { foreach(self::getAggregableTechnicalFields() as $k => $f) {
@@ -258,7 +272,7 @@ class ElasticsearchOptions
], ],
'camera_model_aggregate' => [ 'camera_model_aggregate' => [
'label' => 'Camera Model', 'label' => 'Camera Model',
'field' => 'metadata_tags.CameraModel.raw', 'field' => 'metadata_tags.CameraModel',
'query' => 'meta.CameraModel:%s', 'query' => 'meta.CameraModel:%s',
], ],
'iso_aggregate' => [ 'iso_aggregate' => [
@@ -270,11 +284,20 @@ class ElasticsearchOptions
'label' => 'Aperture', 'label' => 'Aperture',
'field' => 'metadata_tags.Aperture', 'field' => 'metadata_tags.Aperture',
'query' => 'meta.Aperture=%s', 'query' => 'meta.Aperture=%s',
'output_formatter' => function($value) {
return round($value, 1);
},
], ],
'shutterspeed_aggregate' => [ 'shutterspeed_aggregate' => [
'label' => 'Shutter speed', 'label' => 'Shutter speed',
'field' => 'metadata_tags.ShutterSpeed', 'field' => 'metadata_tags.ShutterSpeed',
'query' => 'meta.ShutterSpeed=%s', 'query' => 'meta.ShutterSpeed=%s',
'output_formatter' => function($value) {
if($value < 1.0 && $value != 0) {
$value = '1/' . round(1.0 / $value);
}
return $value . ' s.';
},
], ],
'flashfired_aggregate' => [ 'flashfired_aggregate' => [
'label' => 'FlashFired', 'label' => 'FlashFired',
@@ -283,6 +306,10 @@ class ElasticsearchOptions
'choices' => [ 'choices' => [
"aggregated (2 values: fired = 0 or 1)" => -1, "aggregated (2 values: fired = 0 or 1)" => -1,
], ],
'output_formatter' => function($value) {
static $map = ['0'=>"No flash", '1'=>"Flash"];
return array_key_exists($value, $map) ? $map[$value] : $value;
},
], ],
'framerate_aggregate' => [ 'framerate_aggregate' => [
'label' => 'FrameRate', 'label' => 'FrameRate',
@@ -322,4 +349,60 @@ class ElasticsearchOptions
]; ];
} }
/**
* @param string $order
* @return bool returns false if order is invalid
*/
public function setPopulateOrder($order)
{
$order = strtoupper($order);
if (in_array($order, [self::POPULATE_ORDER_RID, self::POPULATE_ORDER_MODDATE])) {
$this->populateOrder = $order;
return true;
}
return false;
}
/**
* @return string
*/
public function getPopulateOrderAsSQL()
{
static $orderAsColumn = [
self::POPULATE_ORDER_RID => "`record_id`",
self::POPULATE_ORDER_MODDATE => "`moddate`",
];
// populateOrder IS one of the keys (ensured by setPopulateOrder)
return $orderAsColumn[$this->populateOrder];
}
/**
* @param string $direction
* @return bool returns false if direction is invalid
*/
public function setPopulateDirection($direction)
{
$direction = strtoupper($direction);
if (in_array($direction, [self::POPULATE_DIRECTION_DESC, self::POPULATE_DIRECTION_ASC])) {
$this->populateDirection = $direction;
return true;
}
return false;
}
/**
* @return string
*/
public function getPopulateDirectionAsSQL()
{
// already a SQL word
return $this->populateDirection;
}
} }

View File

@@ -32,7 +32,10 @@ class ElasticsearchRecordHydrator
if (substr($key, 0, strlen($prefix)) == $prefix) { if (substr($key, 0, strlen($prefix)) == $prefix) {
$key = substr($key, strlen($prefix)); $key = substr($key, strlen($prefix));
} }
$highlight[$key] = $value; if (substr($key, -6) == '.light') {
$key = substr($key, 0, strlen($key) - 6);
$highlight[$key] = $value;
}
} }
$record = new ElasticsearchRecord(); $record = new ElasticsearchRecord();
@@ -62,7 +65,7 @@ class ElasticsearchRecordHydrator
$record->setTitles((array) igorw\get_in($data, ['title'], [])); $record->setTitles((array) igorw\get_in($data, ['title'], []));
$record->setCaption((array) igorw\get_in($data, ['caption'], [])); $record->setCaption((array) igorw\get_in($data, ['caption'], []));
$record->setPrivateCaption((array) igorw\get_in($data, ['private_caption'], [])); $record->setPrivateCaption((array) igorw\get_in($data, ['private_caption'], []));
$record->setExif((array) igorw\get_in($data, ['exif'], [])); $record->setExif((array)igorw\get_in($data, ['metadata_tags'], []));
$record->setSubdefs((array) igorw\get_in($data, ['subdefs'], [])); $record->setSubdefs((array) igorw\get_in($data, ['subdefs'], []));
$record->setFlags((array) igorw\get_in($data, ['flags'], [])); $record->setFlags((array) igorw\get_in($data, ['flags'], []));
$record->setHighlight((array) $highlight); $record->setHighlight((array) $highlight);

View File

@@ -26,13 +26,14 @@ class FieldMapping
const TYPE_COMPLETION = 'completion'; const TYPE_COMPLETION = 'completion';
// Number core types // Number core types
const TYPE_FLOAT = 'float'; const TYPE_FLOAT = 'float';
const TYPE_DOUBLE = 'double'; const TYPE_DOUBLE = 'double';
const TYPE_INTEGER = 'integer'; const TYPE_INTEGER = 'integer';
const TYPE_LONG = 'long'; const TYPE_LONG = 'long';
const TYPE_SHORT = 'short'; const TYPE_SHORT = 'short';
const TYPE_BYTE = 'byte'; const TYPE_BYTE = 'byte';
const TYPE_IP = 'ip'; const TYPE_IP = 'ip';
const TYPE_GEO_POINT = 'geo_point';
// Compound types // Compound types
const TYPE_OBJECT = 'object'; const TYPE_OBJECT = 'object';
@@ -48,6 +49,7 @@ class FieldMapping
self::TYPE_SHORT, self::TYPE_SHORT,
self::TYPE_BYTE, self::TYPE_BYTE,
self::TYPE_IP, self::TYPE_IP,
self::TYPE_GEO_POINT,
self::TYPE_OBJECT, self::TYPE_OBJECT,
self::TYPE_COMPLETION self::TYPE_COMPLETION
); );

View File

@@ -16,13 +16,11 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\BulkOperation;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordIndexer;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordQueuer; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\RecordQueuer;
use appbox;
use Closure; use Closure;
use Elasticsearch\Client; use Elasticsearch\Client;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use igorw; use igorw;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
use record_adapter;
use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\Stopwatch\Stopwatch;
use SplObjectStorage; use SplObjectStorage;
@@ -84,20 +82,64 @@ class Indexer
$this->deleteQueue = new SplObjectStorage(); $this->deleteQueue = new SplObjectStorage();
} }
public function createIndex($withMapping = true) /**
* @return Index
*/
public function getIndex()
{ {
$params = array(); return $this->index;
$params['index'] = $this->index->getName(); }
$params['body']['settings']['number_of_shards'] = $this->index->getOptions()->getShards();
$params['body']['settings']['number_of_replicas'] = $this->index->getOptions()->getReplicas();
$params['body']['settings']['analysis'] = $this->index->getAnalysis();
if ($withMapping) { public function createIndex($indexName = null)
$params['body']['mappings'][RecordIndexer::TYPE_NAME] = $this->index->getRecordIndex()->getMapping()->export(); {
$params['body']['mappings'][TermIndexer::TYPE_NAME] = $this->index->getTermIndex()->getMapping()->export(); $aliasName = $this->index->getName();
if($indexName === null) {
$indexName = $aliasName;
} }
$now = sprintf("%s.%06d", Date('YmdHis'), 1000000*explode(' ', microtime())[0]) ;
$indexName .= ('_' . $now);
$params = [
'index' => $indexName,
'body' => [
'settings' => [
'number_of_shards' => $this->index->getOptions()->getShards(),
'number_of_replicas' => $this->index->getOptions()->getReplicas(),
'analysis' => $this->index->getAnalysis()
],
'mappings' => [
RecordIndexer::TYPE_NAME => $this->index->getRecordIndex()->getMapping()->export(),
TermIndexer::TYPE_NAME => $this->index->getTermIndex()->getMapping()->export()
]
// ,
// 'aliases' => [
// $aliasName => []
// ]
]
];
$this->client->indices()->create($params); $this->client->indices()->create($params);
$params = [
'body' => [
'actions' => [
[
'add' => [
'index' => $indexName,
'alias' => $aliasName
]
]
]
]
];
$this->client->indices()->updateAliases($params);
return [
'index' => $indexName,
'alias' => $aliasName
];
} }
public function updateMapping() public function updateMapping()
@@ -126,38 +168,129 @@ class Indexer
]); ]);
} }
/**
* @param string $newIndexName
* @param string $newAliasName
* @return array
*/
public function replaceIndex($newIndexName, $newAliasName)
{
$ret = [];
$oldIndexes = $this->client->indices()->getAlias(
[
'index' => $this->index->getName()
]
);
// delete old alias(es), only one alias on one index should exist
foreach($oldIndexes as $oldIndexName => $data) {
foreach($data['aliases'] as $oldAliasName => $data2) {
$params['body']['actions'][] = [
'remove' => [
'alias' => $oldAliasName,
'index' => $oldIndexName,
]
];
$ret[] = [
'action' => "ALIAS_REMOVE",
'msg' => sprintf('alias "%s" -> "%s" removed', $oldAliasName, $oldIndexName),
'alias' => $oldAliasName,
'index' => $oldIndexName,
];
}
}
// create new alias
$params['body']['actions'][] = [
'add' => [
'alias' => $this->index->getName(),
'index' => $newIndexName,
]
];
$ret[] = [
'action' => "ALIAS_ADD",
'msg' => sprintf('alias "%s" -> "%s" added', $this->index->getName(), $newIndexName),
'alias' => $this->index->getName(),
'index' => $newIndexName,
];
//
$params['body']['actions'][] = [
'remove' => [
'alias' => $newAliasName,
'index' => $newIndexName,
]
];
$ret[] = [
'action' => "ALIAS_REMOVE",
'msg' => sprintf('alias "%s" -> "%s" removed', $newAliasName, $newIndexName),
'alias' => $newAliasName,
'index' => $newIndexName,
];
$this->client->indices()->updateAliases($params);
// delete old index(es), only one index should exist
$params = [
'index' => []
];
foreach($oldIndexes as $oldIndexName => $data) {
$params['index'][] = $oldIndexName;
$ret[] = [
'action' => "INDEX_DELETE",
'msg' => sprintf('index "%s" deleted', $oldIndexName),
'index' => $oldIndexName,
];
}
$this->client->indices()->delete(
$params
);
return $ret;
}
public function populateIndex($what, \databox $databox) public function populateIndex($what, \databox $databox)
{ {
$stopwatch = new Stopwatch(); $stopwatch = new Stopwatch();
$stopwatch->start('populate'); $stopwatch->start('populate');
$this->apply(function (BulkOperation $bulk) use ($what, $databox) { $this->apply(
if ($what & self::THESAURUS) { function (BulkOperation $bulk) use ($what, $databox) {
$this->termIndexer->populateIndex($bulk, $databox); if ($what & self::THESAURUS) {
$this->termIndexer->populateIndex($bulk, $databox);
// Record indexing depends on indexed terms so we need to make // Record indexing depends on indexed terms so we need to make
// everything ready to search // everything ready to search
$bulk->flush(); $bulk->flush();
$this->client->indices()->refresh(); $this->client->indices()->refresh();
$this->client->indices()->clearCache(); }
$this->client->indices()->flushSynced();
}
if ($what & self::RECORDS) { if ($what & self::RECORDS) {
$databox->clearCandidates(); $databox->clearCandidates();
$this->recordIndexer->populateIndex($bulk, $databox); $this->recordIndexer->populateIndex($bulk, $databox);
// Final flush // Final flush
$bulk->flush(); $bulk->flush();
} }
}, $this->index); },
$this->index
);
// Optimize index // Optimize index
$params = array('index' => $this->index->getName()); $this->client->indices()->optimize(
$this->client->indices()->optimize($params); [
'index' => $this->index->getName()
]
);
$event = $stopwatch->stop('populate'); $event = $stopwatch->stop('populate');
printf("Indexation finished in %s min (Mem. %s Mo)", ($event->getDuration()/1000/60), bcdiv($event->getMemory(), 1048576, 2));
return [
'duration' => $event->getDuration(),
'memory' => $event->getMemory()
];
} }
public function migrateMappingForDatabox($databox) public function migrateMappingForDatabox($databox)

View File

@@ -12,6 +12,7 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record; namespace Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record;
use Alchemy\Phrasea\Core\PhraseaTokens; use Alchemy\Phrasea\Core\PhraseaTokens;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\FetcherDelegate; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\FetcherDelegate;
use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\FetcherDelegateInterface; use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\Record\Delegate\FetcherDelegateInterface;
@@ -24,6 +25,7 @@ use PDO;
class Fetcher class Fetcher
{ {
private $databox; private $databox;
private $options;
private $connection; private $connection;
private $statement; private $statement;
private $delegate; private $delegate;
@@ -36,9 +38,10 @@ class Fetcher
private $postFetch; private $postFetch;
private $onDrain; private $onDrain;
public function __construct(databox $databox, array $hydrators, FetcherDelegateInterface $delegate = null) public function __construct(databox $databox,ElasticsearchOptions $options, array $hydrators, FetcherDelegateInterface $delegate = null)
{ {
$this->databox = $databox; $this->databox = $databox;
$this->options = $options;
$this->connection = $databox->get_connection();; $this->connection = $databox->get_connection();;
$this->hydrators = $hydrators; $this->hydrators = $hydrators;
$this->delegate = $delegate ?: new FetcherDelegate(); $this->delegate = $delegate ?: new FetcherDelegate();
@@ -136,7 +139,7 @@ class Fetcher
. " FROM (record r INNER JOIN coll c ON (c.coll_id = r.coll_id))" . " FROM (record r INNER JOIN coll c ON (c.coll_id = r.coll_id))"
. " LEFT JOIN subdef ON subdef.record_id=r.record_id AND subdef.name='document'" . " LEFT JOIN subdef ON subdef.record_id=r.record_id AND subdef.name='document'"
. " -- WHERE" . " -- WHERE"
. " ORDER BY r.record_id DESC" . " ORDER BY " . $this->options->getPopulateOrderAsSQL() . " " . $this->options->getPopulateDirectionAsSQL()
. " LIMIT :offset, :limit"; . " LIMIT :offset, :limit";
$where = $this->delegate->buildWhereClause(); $where = $this->delegate->buildWhereClause();

View File

@@ -81,6 +81,30 @@ class GpsPosition
&& $this->latitude_ref !== null; && $this->latitude_ref !== null;
} }
public function isCompleteComposite()
{
return $this->longitude !== null
&& $this->latitude !== null;
}
public function getCompositeLongitude()
{
if ($this->longitude === null) {
return null;
}
return $this->longitude ;
}
public function getCompositeLatitude()
{
if ($this->latitude === null) {
return null;
}
return $this->latitude;
}
public function getSignedLongitude() public function getSignedLongitude()
{ {
if ($this->longitude === null) { if ($this->longitude === null) {

View File

@@ -121,6 +121,9 @@ SQL;
private function sanitizeValue($value, $type) private function sanitizeValue($value, $type)
{ {
switch ($type) { switch ($type) {
case FieldMapping::TYPE_STRING:
return str_replace("\0", "", $value);
case FieldMapping::TYPE_DATE: case FieldMapping::TYPE_DATE:
return $this->helper->sanitizeDate($value); return $this->helper->sanitizeDate($value);
@@ -152,9 +155,18 @@ SQL;
// Push this tag into object // Push this tag into object
$position->set($tag_name, $value); $position->set($tag_name, $value);
// Try to output complete position // Try to output complete position
if ($position->isComplete()) { if ($position->isCompleteComposite()) {
$records[$id]['metadata_tags']['Longitude'] = $position->getSignedLongitude(); $lon = $position->getCompositeLongitude();
$records[$id]['metadata_tags']['Latitude'] = $position->getSignedLatitude(); $lat = $position->getCompositeLatitude();
$records[$id]['metadata_tags']['Longitude'] = $lon;
$records[$id]['metadata_tags']['Latitude'] = $lat;
$records[$id]["location"] = [
"lat" => $lat,
"lon" => $lon
];
unset($this->gps_position_buffer[$id]); unset($this->gps_position_buffer[$id]);
} }
} }

View File

@@ -61,8 +61,8 @@ class RecordIndex implements MappingProvider
// Collection name (still indexed for facets) // Collection name (still indexed for facets)
$mapping->addStringField('collection_name')->disableAnalysis(); $mapping->addStringField('collection_name')->disableAnalysis();
$mapping->addStringField('uuid')->disableIndexing(); $mapping->addStringField('uuid')->disableAnalysis();
$mapping->addStringField('sha256')->disableIndexing(); $mapping->addStringField('sha256')->disableAnalysis();
$mapping->addStringField('original_name')->disableIndexing(); $mapping->addStringField('original_name')->disableIndexing();
$mapping->addStringField('mime')->disableAnalysis(); $mapping->addStringField('mime')->disableAnalysis();
$mapping->addStringField('type')->disableAnalysis(); $mapping->addStringField('type')->disableAnalysis();
@@ -73,6 +73,8 @@ class RecordIndex implements MappingProvider
$mapping->addIntegerField('height')->disableIndexing(); $mapping->addIntegerField('height')->disableIndexing();
$mapping->addIntegerField('size')->disableIndexing(); $mapping->addIntegerField('size')->disableIndexing();
$mapping->addGeoPointField('location');
$mapping->addDateField('created_on', FieldMapping::DATE_FORMAT_MYSQL_OR_CAPTION); $mapping->addDateField('created_on', FieldMapping::DATE_FORMAT_MYSQL_OR_CAPTION);
$mapping->addDateField('updated_on', FieldMapping::DATE_FORMAT_MYSQL_OR_CAPTION); $mapping->addDateField('updated_on', FieldMapping::DATE_FORMAT_MYSQL_OR_CAPTION);

View File

@@ -45,6 +45,15 @@ class MappingBuilder
return $this->mapping->addField(new FieldMapping($name, FieldMapping::TYPE_INTEGER)); return $this->mapping->addField(new FieldMapping($name, FieldMapping::TYPE_INTEGER));
} }
/**
* @param string $name
* @return FieldMapping
*/
public function addGeoPointField($name)
{
return $this->mapping->addField(new FieldMapping($name, FieldMapping::TYPE_GEO_POINT));
}
/** /**
* @param string $name * @param string $name
* @return FieldMapping * @return FieldMapping

View File

@@ -7,6 +7,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
use Alchemy\Phrasea\SearchEngine\SearchEngineSuggestion; use Alchemy\Phrasea\SearchEngine\SearchEngineSuggestion;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use igorw;
class FacetsResponse class FacetsResponse
{ {
@@ -55,12 +56,21 @@ class FacetsResponse
private function buildBucketsValues($name, $buckets) private function buildBucketsValues($name, $buckets)
{ {
$values = array(); $values = array();
// does this aggregate has an output_formatter ? if not use a equality formatter
/** @var callable $formatter */
$formatter = igorw\get_in(
ElasticsearchOptions::getAggregableTechnicalFields(), [$name, 'output_formatter'],
function($v){return $v;}
);
foreach ($buckets as $bucket) { foreach ($buckets as $bucket) {
if (!isset($bucket['key']) || !isset($bucket['doc_count'])) { if (!isset($bucket['key']) || !isset($bucket['doc_count'])) {
$this->throwAggregationResponseError(); $this->throwAggregationResponseError();
} }
$values[] = array( $values[] = array(
'value' => $bucket['key'], 'value' => $formatter($bucket['key']),
'count' => $bucket['doc_count'], 'count' => $bucket['doc_count'],
'query' => $this->buildQuery($name, $bucket['key']), 'query' => $this->buildQuery($name, $bucket['key']),
); );

Some files were not shown because too many files have changed in this diff Show More