Merge branch 'master' into PHRAS-2739-incorporate-subdefwebhook

This commit is contained in:
Nicolas Maillat
2020-02-26 20:14:11 +01:00
committed by GitHub
122 changed files with 7306 additions and 3391 deletions

View File

@@ -36,7 +36,11 @@ RUN apt-get update \
xpdf \ xpdf \
&& update-locale "LANG=fr_FR.UTF-8 UTF-8" \ && update-locale "LANG=fr_FR.UTF-8 UTF-8" \
&& dpkg-reconfigure --frontend noninteractive locales \ && dpkg-reconfigure --frontend noninteractive locales \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-source extract \
&& docker-php-ext-install -j$(nproc) gd \ && docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install zip exif iconv mbstring pcntl sockets xsl intl pdo_mysql gettext bcmath mcrypt \ && docker-php-ext-install zip exif iconv mbstring pcntl sockets xsl intl pdo_mysql gettext bcmath mcrypt \
&& pecl install redis amqp-1.9.3 zmq-beta imagick-beta \ && pecl install redis amqp-1.9.3 zmq-beta imagick-beta \
@@ -45,10 +49,7 @@ RUN apt-get update \
&& docker-php-source delete \ && docker-php-source delete \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ COPY --from=composer:1.9.1 /usr/bin/composer /usr/bin/composer
&& php -r "if (hash_file('sha384', 'composer-setup.php') === 'a5c698ffe4b8e849a443b120cd5ba38043260d5c4023dbf93e1558871f1f07f58274fc6f4c93bcfd858c6bd0775cd8d1') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
&& php composer-setup.php --install-dir=/usr/local/bin --filename=composer \
&& php -r "unlink('composer-setup.php');"
# Node Installation (node + yarn) # Node Installation (node + yarn)
# Reference : # Reference :
@@ -129,6 +130,12 @@ RUN mkdir -p /var/alchemy/Phraseanet/logs \
FROM phraseanet-system as phraseanet-fpm FROM phraseanet-system as phraseanet-fpm
RUN docker-php-source extract \
&& pecl install xdebug-2.9.0 \
&& docker-php-ext-enable xdebug \
#&& pecl clear-cache \
&& docker-php-source delete
COPY --from=builder --chown=app /var/alchemy /var/alchemy/Phraseanet COPY --from=builder --chown=app /var/alchemy /var/alchemy/Phraseanet
ADD ./docker/phraseanet/ / ADD ./docker/phraseanet/ /
WORKDIR /var/alchemy/Phraseanet WORKDIR /var/alchemy/Phraseanet

View File

@@ -23,7 +23,9 @@ 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;
use Alchemy\Phrasea\Command\CreateCollection; use Alchemy\Phrasea\Command\CreateCollection;
use Alchemy\Phrasea\Command\Collection\ListCollectionCommand;
use Alchemy\Phrasea\Command\Databox\CreateDataboxCommand; use Alchemy\Phrasea\Command\Databox\CreateDataboxCommand;
use Alchemy\Phrasea\Command\Databox\ListDataboxCommand;
use Alchemy\Phrasea\Command\MailTest; use Alchemy\Phrasea\Command\MailTest;
use Alchemy\Phrasea\Command\Compile\Configuration; use Alchemy\Phrasea\Command\Compile\Configuration;
use Alchemy\Phrasea\Command\RecordAdd; use Alchemy\Phrasea\Command\RecordAdd;
@@ -46,6 +48,9 @@ use Alchemy\Phrasea\Command\Task\TaskRun;
use Alchemy\Phrasea\Command\Task\TaskStart; use Alchemy\Phrasea\Command\Task\TaskStart;
use Alchemy\Phrasea\Command\Task\TaskState; use Alchemy\Phrasea\Command\Task\TaskState;
use Alchemy\Phrasea\Command\Task\TaskStop; use Alchemy\Phrasea\Command\Task\TaskStop;
use Alchemy\Phrasea\Command\User\UserCreateCommand;
use Alchemy\Phrasea\Command\User\UserSetPasswordCommand;
use Alchemy\Phrasea\Command\User\UserListCommand;
use Alchemy\Phrasea\Command\UpgradeDBDatas; use Alchemy\Phrasea\Command\UpgradeDBDatas;
require_once __DIR__ . '/../lib/autoload.php'; require_once __DIR__ . '/../lib/autoload.php';
@@ -108,8 +113,18 @@ $cli->command(new \module_console_fieldsRename('fields:rename'));
$cli->command(new \module_console_fieldsMerge('fields:merge')); $cli->command(new \module_console_fieldsMerge('fields:merge'));
$cli->command(new CreateCollection('collection:create')); $cli->command(new CreateCollection('collection:create'));
$cli->command(new ListCollectionCommand('collection:list'));
$cli->command(new ListDataboxCommand('databox:list'));
$cli->command(new CreateDataboxCommand('databox:create')); $cli->command(new CreateDataboxCommand('databox:create'));
$cli->command(new UserCreateCommand('user:create'));
$cli->command(new UserSetPasswordCommand('user:set-password'));
$cli->command(new UserListCommand('user:list'));
$cli->command(new RecordAdd('records:add')); $cli->command(new RecordAdd('records:add'));
$cli->command(new RescanTechnicalDatas('records:rescan-technical-datas')); $cli->command(new RescanTechnicalDatas('records:rescan-technical-datas'));
$cli->command(new BuildMissingSubdefs('records:build-missing-subdefs')); $cli->command(new BuildMissingSubdefs('records:build-missing-subdefs'));

View File

@@ -18,6 +18,18 @@
} }
} }
}, },
{
"type": "package",
"package": {
"name": "exiftool/exiftool",
"version": "11",
"source": {
"url": "https://github.com/exiftool/exiftool.git",
"type": "git",
"reference": "11.84"
}
}
},
{ {
"type": "vcs", "type": "vcs",
"url": "https://github.com/alchemy-fr/tcpdf-clone" "url": "https://github.com/alchemy-fr/tcpdf-clone"
@@ -84,7 +96,7 @@
"league/flysystem": "^1.0", "league/flysystem": "^1.0",
"league/flysystem-aws-s3-v2": "^1.0", "league/flysystem-aws-s3-v2": "^1.0",
"league/fractal": "dev-webgalleries#af1acc0275438571bc8c1d08a05a4b5af92c9f97 as 0.13.0", "league/fractal": "dev-webgalleries#af1acc0275438571bc8c1d08a05a4b5af92c9f97 as 0.13.0",
"media-alchemyst/media-alchemyst": "^0.5", "media-alchemyst/media-alchemyst": "^0.5.5",
"monolog/monolog": "~1.3", "monolog/monolog": "~1.3",
"mrclay/minify": "~2.1.6", "mrclay/minify": "~2.1.6",
"neutron/process-manager": "2.0.x-dev@dev", "neutron/process-manager": "2.0.x-dev@dev",
@@ -95,7 +107,7 @@
"pagerfanta/pagerfanta": "^1.0", "pagerfanta/pagerfanta": "^1.0",
"php-ffmpeg/php-ffmpeg": "~0.5.0", "php-ffmpeg/php-ffmpeg": "~0.5.0",
"php-xpdf/php-xpdf": "~0.2.1", "php-xpdf/php-xpdf": "~0.2.1",
"phpexiftool/exiftool": "10.10", "exiftool/exiftool": "^11",
"ramsey/uuid": "^3.0", "ramsey/uuid": "^3.0",
"roave/security-advisories": "dev-master", "roave/security-advisories": "dev-master",
"silex/silex": "^1.3.0", "silex/silex": "^1.3.0",

463
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,13 +22,54 @@ main:
path: '/tmp/db.sqlite' path: '/tmp/db.sqlite'
charset: UTF8 charset: UTF8
cache: cache:
type: MemcacheCache type: redis
options: options:
host: localhost host: localhost
port: 11211 port: 6379
search-engine: search-engine:
type: phrasea type: elasticsearch
options: [] options:
host: elasticsearch
port: 9200
index: ''
shards: 3
replicas: 0
minScore: 2
highlight: true
populate_order: RECORD_ID
populate_direction: DESC
activeTab: ''
facets:
_base:
limit: 10
_collection:
limit: 10
_doctype:
limit: 10
_camera_model:
limit: 0
_iso:
limit: 0
_aperture:
limit: 0
_shutterspeed:
limit: 0
_flashfired:
limit: 0
_framerate:
limit: 0
_audiosamplerate:
limit: 0
_videocodec:
limit: 0
_audiocodec:
limit: 0
_orientation:
limit: 0
_colorspace:
limit: 0
_mimetype:
limit: 0
task-manager: task-manager:
status: started status: started
enabled: true enabled: true
@@ -62,12 +103,7 @@ main:
mp4box_timeout: 60 mp4box_timeout: 60
swftools_timeout: 60 swftools_timeout: 60
unoconv_timeout: 60 unoconv_timeout: 60
task-manager: exiftool_timeout: 60
status: started
listener:
protocol: tcp
host: 127.0.0.1
port: 6700
storage: storage:
subdefs: null subdefs: null
cache: null cache: null
@@ -75,20 +111,7 @@ main:
download: null download: null
lazaret: null lazaret: null
caption: null caption: null
bridge: tmp_files: null
youtube:
enabled: false
client_id: null
client_secret: null
developer_key: null
flickr:
enabled: false
client_id: null
client_secret: null
dailymotion:
enabled: false
client_id: null
client_secret: null
border-manager: border-manager:
enabled: true enabled: true
extension-mapping: extension-mapping:
@@ -98,12 +121,17 @@ border-manager:
- -
type: Checker\Sha256 type: Checker\Sha256
enabled: true enabled: true
collections: []
compare-ignore-collections: []
- -
type: Checker\UUID type: Checker\UUID
enabled: true enabled: true
collections: []
compare-ignore-collections: []
- -
type: Checker\Colorspace type: Checker\Colorspace
enabled: false enabled: false
collections: []
options: options:
colorspaces: [cmyk, grayscale, rgb] colorspaces: [cmyk, grayscale, rgb]
media_types: [Image] media_types: [Image]
@@ -123,6 +151,8 @@ border-manager:
enabled: false enabled: false
options: options:
sensitive: true sensitive: true
collections: []
compare-ignore-collections: []
- -
type: Checker\MediaType type: Checker\MediaType
enabled: false enabled: false
@@ -244,6 +274,14 @@ embed_bundle:
document: document:
player: flexpaper player: flexpaper
enable_pdfjs: true enable_pdfjs: true
video-editor:
ChapterVttFieldName: VideoTextTrackChapters
seekBackwardStep: 500 # in ms
seekForwardStep: 500 # in ms
playbackRates:
- 1
- '1.5'
- 3
geocoding-providers: geocoding-providers:
- -
map-provider: mapboxWebGL map-provider: mapboxWebGL

View File

@@ -27,9 +27,10 @@ fi
/var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.host elasticsearch /var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.host elasticsearch
/var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.minScore 2 /var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.minScore 2
/var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.base_aggregate_limit 10 /var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.minScore 2
/var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.collection_aggregate_limit 10 /var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.facets._base.limit 10
/var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.doctype_aggregate_limit 10 /var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.facets._collection.limit 10
/var/alchemy/Phraseanet/bin/setup system:config set main.search-engine.options.facets._doctype.limit 10
## Redis ## Redis
/var/alchemy/Phraseanet/bin/setup system:config set main.cache.options.host redis /var/alchemy/Phraseanet/bin/setup system:config set main.cache.options.host redis

View File

@@ -14,4 +14,4 @@ else
runuser app -c '/auto-install.sh' runuser app -c '/auto-install.sh'
fi fi
php-fpm php-fpm -F

View File

@@ -4,5 +4,16 @@ set -e
envsubst < /php.ini.sample > /usr/local/etc/php/php.ini envsubst < /php.ini.sample > /usr/local/etc/php/php.ini
envsubst < /php-fpm.conf.sample > /usr/local/etc/php-fpm.conf envsubst < /php-fpm.conf.sample > /usr/local/etc/php-fpm.conf
echo "XDEBUG=$XDEBUG"
if [ $XDEBUG = "ON" ]; then
echo "XDEBUG IS ENABLED. YOU MAY KEEP THIS FEATURE DISABLED IN PRODUCTION."
echo "xdebug.remote_enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_autostart=1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_host=$XDEBUG_SERVER" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_port=$XDEBUG_REMOTE_PORT" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_handler=dbgp" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
echo "xdebug.remote_connect_back=0" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
#echo "xdebug.idekey=11896" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
fi
bash -e docker-php-entrypoint $@ bash -e docker-php-entrypoint $@

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\ControllerProvider\Api\OAuth2; use Alchemy\Phrasea\ControllerProvider\Api\OAuth2;
use Alchemy\Phrasea\ControllerProvider\Api\V1; use Alchemy\Phrasea\ControllerProvider\Api\V1;
use Alchemy\Phrasea\ControllerProvider\Api\V2; use Alchemy\Phrasea\ControllerProvider\Api\V2;
use Alchemy\Phrasea\ControllerProvider\Api\V3;
use Alchemy\Phrasea\ControllerProvider\Datafiles; use Alchemy\Phrasea\ControllerProvider\Datafiles;
use Alchemy\Phrasea\ControllerProvider\MediaAccessor; use Alchemy\Phrasea\ControllerProvider\MediaAccessor;
use Alchemy\Phrasea\ControllerProvider\Minifier; use Alchemy\Phrasea\ControllerProvider\Minifier;
@@ -36,6 +37,7 @@ class ApiApplicationLoader extends BaseApplicationLoader
$app->register(new OAuth2()); $app->register(new OAuth2());
$app->register(new V1()); $app->register(new V1());
$app->register(new V2()); $app->register(new V2());
$app->register(new V3());
$app->register(new ApiReportControllerProvider()); $app->register(new ApiReportControllerProvider());
$app->register(new JsonSchemaServiceProvider()); $app->register(new JsonSchemaServiceProvider());
} }
@@ -119,6 +121,16 @@ class ApiApplicationLoader extends BaseApplicationLoader
'access_token' => '/api/oauthv2/token' 'access_token' => '/api/oauthv2/token'
], ],
], ],
'3' => [
'number' => V3::VERSION,
'uri' => '/api/v3/',
'authenticationProtocol' => 'OAuth2',
'authenticationVersion' => 'draft#v9',
'authenticationEndPoints' => [
'authorization_token' => '/api/oauthv2/authorize',
'access_token' => '/api/oauthv2/token'
]
],
] ]
])->createResponse(); ])->createResponse();
}); });
@@ -135,6 +147,7 @@ class ApiApplicationLoader extends BaseApplicationLoader
$app->mount('/datafiles/', new Datafiles()); $app->mount('/datafiles/', new Datafiles());
$app->mount('/api/v1', new V1()); $app->mount('/api/v1', new V1());
$app->mount('/api/v2', new V2()); $app->mount('/api/v2', new V2());
$app->mount('/api/v3', new V3());
$app->mount('/api/report', new ApiReportControllerProvider()); $app->mount('/api/report', new ApiReportControllerProvider());
$app->mount('/permalink/', new Permalink()); $app->mount('/permalink/', new Permalink());
$app->mount($app['controller.media_accessor.route_prefix'], new MediaAccessor()); $app->mount($app['controller.media_accessor.route_prefix'], new MediaAccessor());

View File

@@ -0,0 +1,86 @@
<?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\Collection;
use Alchemy\Phrasea\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ListCollectionCommand extends Command
{
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('collection:list');
$this->setDescription('List all collection in Phraseanet')
->addOption('databox_id', 'd', InputOption::VALUE_REQUIRED, 'The id of the databox to list collection')
->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
try {
$jsonformat = $input->getOption('jsonformat');
$databox = $this->container->findDataboxById($input->getOption('databox_id'));
$collections = $this->listDataboxCollections($databox);
if ($jsonformat) {
foreach ($collections as $collection) {
$collectionList[] = array_combine(['id local for API', 'id distant', 'name','label','status','total records'], $collection);
}
echo json_encode($collectionList);
} else {
$table = $this->getHelperSet()->get('table');
$table
->setHeaders(['id local for API', 'id distant', 'name','label','status','total records'])
->setRows($collections)
->render($output);
}
} catch (\Exception $e) {
$output->writeln("<error>{$e->getMessage()}</error>");
}
return 0;
}
private function listDataboxCollections(\databox $databox)
{
return array_map(function (\collection $collection) {
return $this->listCollection($collection);
}, array_merge($databox->get_collections(),$this->getUnabledCollection($databox->get_activable_colls())));
}
private function getUnabledCollection($collections)
{
return array_map(function ($colId){
return \collection::getByBaseId($this->container, $colId);
},$collections);
}
private function listCollection(\collection $collection)
{
return [
$collection->get_base_id(),
$collection->get_coll_id(),
$collection->get_name(),
'en: ' . $collection->get_label('en') .
', de: ' . $collection->get_label('de') .
', fr: ' . $collection->get_label('fr') .
', nl: ' . $collection->get_label('nl'),
($collection->is_active()) ? 'enabled' : 'disabled',
$collection->get_record_amount()
];
}
}

View File

@@ -0,0 +1,72 @@
<?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\Databox;
use Alchemy\Phrasea\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ListDataboxCommand extends Command
{
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('databox:list');
$this->setDescription('List all databox in Phraseanet')
->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
try {
$jsonformat = $input->getOption('jsonformat');
$databoxes = array_map(function (\databox $databox) {
return $this->listDatabox($databox);
}, $this->container->getApplicationBox()->get_databoxes());
if ($jsonformat) {
foreach ($databoxes as $databox) {
$databoxList[] = array_combine(['id', 'name', 'alias'], $databox);
}
echo json_encode($databoxList);
} else {
$table = $this->getHelperSet()->get('table');
$table
->setHeaders(['id', 'name', 'alias'])
->setRows($databoxes)
->render($output);
}
} catch (\Exception $e) {
$output->writeln('<error>Listing databox failed : '.$e->getMessage().'</error>');
}
return 0;
}
private function listDatabox(\databox $databox)
{
return [
$databox->get_sbas_id(),
$databox->get_dbname(),
$databox->get_viewname()
];
}
}

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Command\Command; use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Exception\RuntimeException;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Plugin\Plugin;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\Client; use Symfony\Component\HttpKernel\Client;
@@ -30,6 +31,15 @@ class JsFixtures extends Command
protected function doExecute(InputInterface $input, OutputInterface $output) protected function doExecute(InputInterface $input, OutputInterface $output)
{ {
/** @var Plugin $plugin */
$msg = [];
foreach($this->container['plugins.manager']->listPlugins() as $plugin) {
$msg[] = sprintf(" bin/setup plugins:remove \"%s\"", $plugin->getName());
}
if(count($msg) !== 0) {
throw new RuntimeException("You must remove plugins first:\n" . join("\n", $msg));
}
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/developer phraseanet:regenerate-sqlite" command.'); throw new RuntimeException('You must generate sqlite db first, run "bin/developer phraseanet:regenerate-sqlite" command.');
} }
@@ -104,6 +114,7 @@ class JsFixtures extends Command
private function writeResponse(OutputInterface $output, $method, $path, $to, $authenticateUser = false) private function writeResponse(OutputInterface $output, $method, $path, $to, $authenticateUser = false)
{ {
$environment = Application::ENV_TEST; $environment = Application::ENV_TEST;
/** @var Application $app */
$app = require __DIR__ . '/../../Application/Root.php'; $app = require __DIR__ . '/../../Application/Root.php';
$app['orm.em'] = $app->extend('orm.em', function($em, $app) { $app['orm.em'] = $app->extend('orm.em', function($em, $app) {
return $app['orm.ems'][$app['db.fixture.hash.key']]; return $app['orm.ems'][$app['db.fixture.hash.key']];

View File

@@ -0,0 +1,211 @@
<?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\User;
use Alchemy\Phrasea\Application\Helper\NotifierAware;
use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Core\LazyLocator;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Notification\Mail\MailRequestPasswordSetup;
use Alchemy\Phrasea\Notification\Mail\MailRequestEmailConfirmation;
use Alchemy\Phrasea\Notification\Receiver;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class UserCreateCommand extends Command
{
use NotifierAware;
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('user:create');
$this->setDescription('Create user in Phraseanet')
->addOption('user_login', null, InputOption::VALUE_REQUIRED, 'The desired login for created user.')
->addOption('user_mail', null, InputOption::VALUE_OPTIONAL, 'The desired mail for created user.')
->addOption('user_password', null, InputOption::VALUE_OPTIONAL, 'The desired password')
->addOption('send_mail_confirm', null, InputOption::VALUE_NONE, 'Send an email to user, for validate email.')
->addOption('send_mail_password', null, InputOption::VALUE_NONE, 'Send an email to user, for password definition, work only if user_password is not define')
->addOption('model_number', null, InputOption::VALUE_OPTIONAL, 'Id of model')
->addOption('user_gender', null, InputOption::VALUE_OPTIONAL, 'The gender for created user.')
->addOption('user_firstname', null, InputOption::VALUE_OPTIONAL, 'The first name for created user.')
->addOption('user_lastname', null, InputOption::VALUE_OPTIONAL, 'The last name for created user.')
->addOption('user_compagny', null, InputOption::VALUE_OPTIONAL, 'The compagny for created user.')
->addOption('user_job', null, InputOption::VALUE_OPTIONAL, 'The job for created user.')
->addOption('user_activitie', null, InputOption::VALUE_OPTIONAL, 'The activitie for created user.')
->addOption('user_phone', null, InputOption::VALUE_OPTIONAL, 'The phone number for created user.')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$userLogin = $input->getOption('user_login');
$userMail = $input->getOption('user_mail');
$userPassword = $input->getOption('user_password');
$sendMailConfirm = $input->getOption('send_mail_confirm');
$sendMailPassword = $input->getOption('send_mail_password');
$modelNumber = $input->getOption('model_number');
$userGender = $input->getOption('user_gender');
$userFirstName = $input->getOption('user_firstname');
$userLastName = $input->getOption('user_lastname');
$userCompagny = $input->getOption('user_compagny');
$userJob = $input->getOption('user_job');
$userActivity = $input->getOption('user_activitie');
$userPhone = $input->getOption('user_phone');
$userRepository = $this->container['repo.users'];
if ($userMail) {
if (!\Swift_Validate::email($userMail)) {
$output->writeln('<error>Invalid mail address</error>');
return 0;
}
if (null !== $userRepository->findByEmail($userMail)) {
$output->writeln('<error>An user exist with this email.</error>');
return 0;
}
}
$password = (!is_null($userPassword)) ? $userPassword : $this->container['random.medium']->generateString(128);
$userManipulator = $this->container['manipulator.user'];
$user = $userManipulator->createUser($userLogin, $password, $userMail);
if ($userGender) {
if (null === $gender = $this->verifyGender($userGender)) {
$output->writeln('<bg=yellow;options=bold>Gender '.$userGender.' not exists.</>');
}
$user->setGender($gender);
}
if($userFirstName) $user->setFirstName($userFirstName);
if($userLastName) $user->setLastName($userLastName);
if($userCompagny) $user->setCompany($userCompagny);
if($userJob) $user->setJob($userJob);
if($userActivity) $user->setActivity($userActivity);
if($userPhone) $user->setPhone($userPhone);
if ($sendMailPassword and $userMail and is_null($userPassword)) {
$this->sendPasswordSetupMail($user);
}
if ($sendMailConfirm and $userMail) {
$user->setMailLocked(true);
$this->sendAccountUnlockEmail($user);
}
if ($modelNumber) {
$template = $userRepository->find($modelNumber);
if (!$template) {
$output->writeln('<bg=yellow;options=bold>Model '.$modelNumber.' not found.</>');
} else {
$base_ids = [];
foreach ($this->container->getApplicationBox()->get_databoxes() as $databox) {
foreach ($databox->get_collections() as $collection) {
$base_ids[] = $collection->get_base_id();
}
}
$this->container->getAclForUser($user)->apply_model($template, $base_ids);
}
}
$this->container['orm.em']->flush();
$output->writeln("<info>Create new user successful !</info>");
return 0;
}
/**
* Get gender for user
* @param $type
* @return int|null
*/
private function verifyGender($type)
{
switch (strtolower($type)) {
case "mlle.":
case "mlle":
case "miss":
case "mademoiselle":
case "0":
$gender = User::GENDER_MISS;
break;
case "mme":
case "madame":
case "ms":
case "ms.":
case "1":
$gender = User::GENDER_MRS;
break;
case "m":
case "m.":
case "mr":
case "mr.":
case "monsieur":
case "mister":
case "2":
$gender = User::GENDER_MR;
break;
default:
$gender = null;
}
return $gender;
}
/**
* Send mail for renew password
* @param User $user
*/
public function sendPasswordSetupMail(User $user)
{
$this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer'));
$receiver = Receiver::fromUser($user);
$token = $this->container['manipulator.token']->createResetPasswordToken($user);
$mail = MailRequestPasswordSetup::create($this->container, $receiver);
$servername = $this->container['conf']->get('servername');
$mail->setButtonUrl('http://'.$servername.'/login/renew-password/?token='.$token->getValue());
$mail->setLogin($user->getLogin());
$this->deliver($mail);
}
/**
* @param User $user
*/
public function sendAccountUnlockEmail(User $user)
{
$this->setDelivererLocator(new LazyLocator($this->container, 'notification.deliverer'));
$receiver = Receiver::fromUser($user);
$token = $this->container['manipulator.token']->createAccountUnlockToken($user);
$mail = MailRequestEmailConfirmation::create($this->container, $receiver);
$servername = $this->container['conf']->get('servername');
$mail->setButtonUrl('http://'.$servername.'/login/register-confirm/?code='.$token->getValue());
$mail->setExpiration($token->getExpiration());
$this->deliver($mail);
}
}

View File

@@ -0,0 +1,287 @@
<?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\User;
use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\Model\Entities\User;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Alchemy\Phrasea\Utilities\NullableDateTime;
class UserListCommand extends Command
{
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('user:list');
$this->setDescription('List of all user (experimental)')
->addOption('user_id', null, InputOption::VALUE_OPTIONAL, ' The id of user export only info this user ')
->addOption('user_email', null, InputOption::VALUE_OPTIONAL, 'The mail of user export only info this user .')
->addOption('database_id', null, InputOption::VALUE_OPTIONAL, 'Id of database.')
->addOption('collection_id', null, InputOption::VALUE_OPTIONAL, 'Id of the collection.')
->addOption('mail_lock_status', null, InputOption::VALUE_NONE, 'Status by mail locked')
->addOption('guest', null, InputOption::VALUE_NONE, 'Only guest user')
->addOption('created', null, InputOption::VALUE_OPTIONAL, 'Created at with operator,aaaa-mm-jj hh:mm:ss.')
->addOption('updated', null, InputOption::VALUE_OPTIONAL, 'Update at with operator,aaaa-mm-jj hh:mm:ss.')
->addOption('application', null, InputOption::VALUE_NONE, 'List application of user work only if --user_id is set')
->addOption('right', null, InputOption::VALUE_NONE, 'Show right information')
->addOption('adress', null, InputOption::VALUE_NONE, 'Show adress information')
->addOption('models', null, InputOption::VALUE_NONE, "Show only defined models, if --user_id is set with --models it's the template owner")
->addOption('jsonformat', null, InputOption::VALUE_NONE, 'Output in json format')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$userId = $input->getOption('user_id');
$userEmail = $input->getOption('user_email');
$databaseId = $input->getOption('database_id');
$collectionId = $input->getOption('collection_id');
$lockStatus = $input->getOption('mail_lock_status');
$guest = $input->getOption('guest');
$application = $input->getOption('application');
$withAdress = $input->getOption('adress');
$created = $input->getOption('created');
$updated = $input->getOption('updated');
$withRight = $input->getOption('right');
$models = $input->getOption('models');
$jsonformat = $input->getOption('jsonformat');
$query = $this->container['phraseanet.user-query'];
if($databaseId) $query->on_base_ids([$databaseId]);
if($collectionId) $query->on_sbas_ids([$collectionId]);
if($created) $this->addFilterDate($created,'created',$query);
if($updated) $this->addFilterDate($updated,'updated',$query);
if($userId && !$models) $query->addSqlFilter('Users.id = ?' ,[$userId]);
if($userEmail && !$models) $query->addSqlFilter('Users.email = ?' ,[$userEmail]);
if($lockStatus && !$models) $query->addSqlFilter('Users.mail_locked = 1');
if($guest && !$models) $query->include_invite(true)->addSqlFilter('Users.guest = 1');
if ($application and !$userId) {
$output->writeln('<error>You must provide --user_id when using --application option</error>');
return 0;
}
/** @var UserRepository $userRepository */
$userRepository = $this->container['repo.users'];
if ($models && $userId) {
$users = $userRepository->findBy(['templateOwner' => $userId]);
} elseif ($models) {
$users = $userRepository->findTemplate();
} else {
$users = $query->execute()->get_results();
}
$userList = [];
$showApplication = false;
foreach ($users as $key => $user) {
if ($userId and $application) {
$showApplication = true;
}
$userList[] = $this->listUser($user, $withAdress, $withRight);
$userListRaw[] = array_combine($this->headerTable($withAdress, $withRight), $this->listUser($user, $withAdress, $withRight));
}
if ($jsonformat) {
echo json_encode($userListRaw);
} else {
$table = $this->getHelperSet()->get('table');
$table
->setHeaders($this->headerTable($withAdress, $withRight))
->setRows($userList)
->render($output);
;
if ($showApplication) {
$applicationTable = $this->getHelperSet()->get('table');
$applicationTable->setHeaders(array(
array(new TableCell('Applications', array('colspan' => 5))),
['name','callback','client_secret','client_id','token'],
))->setRows($this->getApplicationOfUser($users[0]))->render($output);
}
}
return 0;
}
/**
* @param $withAdress
* @param $withRight
* @return array
*/
private function headerTable($withAdress,$withRight)
{
$defaultHeader = ['id', 'login', 'email','last_model','first_name','last_name','gender','created','updated','status','locale'];
$adressHeader = [ 'address', 'zip_code', 'city', 'country', 'phone', 'fax', 'job','position', 'company', 'geoname_id'];
$rightHeader = [ 'admin', 'guest', 'mail_notification', 'ldap_created', 'mail_locked'];
return $this->createInformation($withAdress,$withRight,$defaultHeader,['adress' => $adressHeader,'right' =>$rightHeader]);
}
/**
* @param User $user
* @param $withAdress
* @param $withRight
* @return array
*/
private function listUser(User $user,$withAdress,$withRight)
{
switch ($user->getGender()) {
case User::GENDER_MRS:
$gender = 'Mrs';
break;
case User::GENDER_MISS:
$gender = 'Miss';
break;
case User::GENDER_MR:
default:
$gender = 'Mr';
}
$defaultInfo = [
$user->getId(),
$user->getLogin() ?: '-',
$user->getEmail() ?: '-',
$user->getLastAppliedTemplate() ? $user->getLastAppliedTemplate()->getLogin() : '-',
$user->getFirstName() ?: '-',
$user->getLastName() ?: '-',
$gender,
NullableDateTime::format($user->getCreated(),'Y-m-d H:i:s'),
NullableDateTime::format($user->getUpdated(),'Y-m-d H:i:s'),
'status',
$user->getLocale() ?: '-',
];
return $this->createInformation($withAdress,$withRight,$defaultInfo,['adress' => $this->userAdress($user),'right' => $this->userRight($user)]);
}
/**
* @param User $user
* @return array
*/
private function userAdress(User $user)
{
return [
$user->getAddress() ?: '-',
$user->getZipCode() ?: '-',
$user->getCity() ?: '-',
$user->getCountry() ?: '-',
$user->getPhone() ?: '-',
$user->getFax() ?: '-',
$user->getJob() ?: '-',
$user->getActivity() ?: '-',
$user->getCompany() ?: '-',
$user->getGeonameId() ?: '-',
];
}
/**
* @param User $user
* @return array
*/
private function userRight(User $user)
{
return [
$user->isAdmin() ?: false,
$user->isGuest() ?: false,
$user->hasMailNotificationsActivated() ?: false,
$user->hasLdapCreated() ?: false,
$user->isMailLocked() ?: false,
];
}
/**
* @param User $user
* @return array
*/
private function getApplicationOfUser(User $user)
{
$apiRepository = $this->container['repo.api-applications'];
$applications = $apiRepository->findByUser($user);
if (empty($applications)) {
return [];
}
$accountRepository = $this->container['repo.api-accounts'];
$apiOauthRepository = $this->container['repo.api-oauth-tokens'];
$usersApplication = [];
foreach ($applications as $application) {
$account = $accountRepository->findByUserAndApplication($user, $application);
$token = $account ? $apiOauthRepository->findDeveloperToken($account) : null;
$usersApplication[] = [
$application->getName(),
$application->getRedirectUri(),
$application->getClientSecret(),
$application->getClientId(),
($token) ? $token->getOauthToken() : '-'
];
}
return $usersApplication;
}
/**
* @param $withAdress
* @param $withRight
* @param $default
* @param $infoToMerge
* @return array
*/
private function createInformation($withAdress,$withRight,$default,$infoToMerge)
{
if ($withAdress && $withRight) {
$information = array_merge($default, $infoToMerge['adress'],$infoToMerge['right']);
} elseif ($withAdress && !$withRight) {
$information = array_merge($default, $infoToMerge['adress']);
} elseif(!$withAdress && $withRight) {
$information = array_merge($default, $infoToMerge['right']);
} else {
$information = $default;
}
return $information;
}
/**
* @param $date
* @param $type
* @param $query
*/
private function addFilterDate($date,$type,$query){
list($operator,$dateAt) = explode(',', $date);
if (!in_array($operator,['=','>=','<=','>','<'])) {
throw new \InvalidArgumentException(" '=' or '<=' or '>=' or '>' or '<'");
}
$query->addSqlFilter($type.$operator.' ?' ,[$dateAt]);
}
}

View File

@@ -0,0 +1,79 @@
<?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\User;
use Alchemy\Phrasea\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
class UserSetPasswordCommand extends Command
{
/**
* Constructor
*/
public function __construct($name = null)
{
parent::__construct('user:set-password');
$this->setDescription('Set user password in Phraseanet')
->addOption('user_id', null, InputOption::VALUE_REQUIRED, 'The id of user.')
->addOption('generate', null, InputOption::VALUE_NONE, 'Generate the password')
->addOption('password', null, InputOption::VALUE_OPTIONAL, 'The password')
->setHelp('');
return $this;
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelperSet()->get('dialog');
$userRepository = $this->container['repo.users'];
$userManipulator = $this->container['manipulator.user'];
$user = $userRepository->find($input->getOption('user_id'));
$password = $input->getOption('password');
$generate = $input->getOption('generate');
if ($user === null) {
$output->writeln('<info>Not found User.</info>');
return 0;
}
if ($generate) {
$password = $this->container['random.medium']->generateString(64);
} else {
if (!$password) {
$output->writeln('<error>--password option not specified</error>');
return 0;
}
}
do {
$continue = mb_strtolower($dialog->ask($output, '<question>Do you want really set password to this user? (y/N)</question>', 'N'));
} while (!in_array($continue, ['y', 'n']));
if ($continue !== 'y') {
$output->writeln('Aborting !');
return;
}
$userManipulator->setPassword($user,$password);
$output->writeln('New password: <info>' . $password . '</info>');
return 0;
}
}

View File

@@ -13,9 +13,11 @@ namespace Alchemy\Phrasea\Controller\Admin;
use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType; use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions; use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use databox_descriptionStructure;
class SearchEngineController extends Controller class SearchEngineController extends Controller
{ {
@@ -31,7 +33,19 @@ class SearchEngineController extends Controller
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {
$this->saveElasticSearchOptions($form->getData()); /** @var ElasticsearchOptions $data */
$data = $form->getData();
// $q = $request->request->get('elasticsearch_settings');
$facetNames = []; // rebuild the data "_customValues/facets" list following the form order
foreach($request->request->get('elasticsearch_settings') as $name=>$value) {
$matches = null;
if(preg_match('/^facets:(.+):limit$/', $name, $matches) === 1) {
$facetNames[] = $matches[1];
}
}
$data->reorderAggregableFields($facetNames);
$this->saveElasticSearchOptions($data);
return $this->app->redirectPath('admin_searchengine_form'); return $this->app->redirectPath('admin_searchengine_form');
} }
@@ -76,6 +90,16 @@ class SearchEngineController extends Controller
*/ */
private function saveElasticSearchOptions(ElasticsearchOptions $configuration) private function saveElasticSearchOptions(ElasticsearchOptions $configuration)
{ {
// save to databoxes fields for backward compatibility (useless ?)
foreach($configuration->getAggregableFields() as $fname=>$aggregableField) {
foreach ($this->app->getDataboxes() as $databox) {
if(!is_null($f = $databox->get_meta_structure()->get_element_by_name($fname, databox_descriptionStructure::STRICT_COMPARE))) {
$f->set_aggregable($aggregableField['limit'])->save();
}
}
}
// save to conf
$this->getConf()->set(['main', 'search-engine', 'options'], $configuration->toArray()); $this->getConf()->set(['main', 'search-engine', 'options'], $configuration->toArray());
} }
@@ -85,7 +109,10 @@ class SearchEngineController extends Controller
*/ */
private function getConfigurationForm(ElasticsearchOptions $options) private function getConfigurationForm(ElasticsearchOptions $options)
{ {
return $this->app->form(new ElasticsearchSettingsFormType(), $options, [ /** @var GlobalStructure $g */
$g = $this->app['search_engine.structure'];
return $this->app->form(new ElasticsearchSettingsFormType($g, $options), $options, [
'action' => $this->app->url('admin_searchengine_form'), 'action' => $this->app->url('admin_searchengine_form'),
]); ]);
} }

View File

@@ -12,6 +12,7 @@
namespace Alchemy\Phrasea\Controller\Api; namespace Alchemy\Phrasea\Controller\Api;
use Alchemy\Phrasea\ControllerProvider\Api\V1; use Alchemy\Phrasea\ControllerProvider\Api\V1;
use Alchemy\Phrasea\ControllerProvider\Api\V3;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -268,11 +269,13 @@ class Result
public function getVersion() public function getVersion()
{ {
if (null === $this->version) { if (null === $this->version) {
if($this->request->attributes->get('api_version')){ if ($this->request->attributes->get('api_version')) {
$this->version = $this->request->attributes->get('api_version'); $this->version = $this->request->attributes->get('api_version');
}elseif(mb_strpos($this->request->getPathInfo(), '/api/v1') !== FALSE){ } elseif (mb_strpos($this->request->getPathInfo(), '/api/v1') !== FALSE) {
$this->version = V1::VERSION; $this->version = V1::VERSION;
}else{ } elseif (mb_strpos($this->request->getPathInfo(), '/api/v3') !== FALSE) {
$this->version = V3::VERSION;
} else {
$this->version = self::$defaultVersion; $this->version = self::$defaultVersion;
} }
} }

View File

@@ -0,0 +1,807 @@
<?php
namespace Alchemy\Phrasea\Controller\Api;
use Alchemy\Phrasea\Collection\Reference\CollectionReference;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Databox\DataboxGroupable;
use Alchemy\Phrasea\Fractal\CallbackTransformer;
use Alchemy\Phrasea\Fractal\IncludeResolver;
use Alchemy\Phrasea\Fractal\SearchResultTransformerResolver;
use Alchemy\Phrasea\Fractal\TraceableArraySerializer;
use Alchemy\Phrasea\Model\Manipulator\UserManipulator;
use Alchemy\Phrasea\Model\RecordReferenceInterface;
use Alchemy\Phrasea\Record\RecordCollection;
use Alchemy\Phrasea\Record\RecordReferenceCollection;
use Alchemy\Phrasea\Search\CaptionView;
use Alchemy\Phrasea\Search\PermalinkTransformer;
use Alchemy\Phrasea\Search\PermalinkView;
use Alchemy\Phrasea\Search\RecordTransformer;
use Alchemy\Phrasea\Search\RecordView;
use Alchemy\Phrasea\Search\SearchResultView;
use Alchemy\Phrasea\Search\StoryTransformer;
use Alchemy\Phrasea\Search\StoryView;
use Alchemy\Phrasea\Search\SubdefTransformer;
use Alchemy\Phrasea\Search\SubdefView;
use Alchemy\Phrasea\Search\TechnicalDataTransformer;
use Alchemy\Phrasea\Search\TechnicalDataView;
use Alchemy\Phrasea\Search\V1SearchCompositeResultTransformer;
use Alchemy\Phrasea\Search\V1SearchResultTransformer;
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
use Alchemy\Phrasea\SearchEngine\SearchEngineLogger;
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
use Alchemy\Phrasea\SearchEngine\SearchEngineResult;
use League\Fractal\Resource\Item;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class V3Controller extends Controller
{
/**
* Return detailed information about one story
*
* @param Request $request
* @param int $databox_id
* @param int $record_id
*
* @return Response
*/
public function getStoryAction(Request $request, $databox_id, $record_id)
{
try {
$story = $this->findDataboxById($databox_id)->get_record($record_id);
return Result::create($request, ['story' => $this->listStory($request, $story)])->createResponse();
} catch (NotFoundHttpException $e) {
return Result::createError($request, 404, $this->app->trans('Story Not Found'))->createResponse();
} catch (\Exception $e) {
return $this->app['controller.api.v1']->getBadRequestAction($request, $this->app->trans('An error occurred'));
}
}
/**
* Search for results
*
* @param Request $request
*
* @return Response
*/
public function searchAction(Request $request)
{
$subdefTransformer = new SubdefTransformer($this->app['acl'], $this->getAuthenticatedUser(), new PermalinkTransformer());
$technicalDataTransformer = new TechnicalDataTransformer();
$recordTransformer = new RecordTransformer($subdefTransformer, $technicalDataTransformer);
$storyTransformer = new StoryTransformer($subdefTransformer, $recordTransformer);
$compositeTransformer = new V1SearchCompositeResultTransformer($recordTransformer, $storyTransformer);
$searchTransformer = new V1SearchResultTransformer($compositeTransformer);
$transformerResolver = new SearchResultTransformerResolver([
'' => $searchTransformer,
'results' => $compositeTransformer,
'results.stories' => $storyTransformer,
'results.stories.thumbnail' => $subdefTransformer,
'results.stories.metadatas' => new CallbackTransformer(),
'results.stories.caption' => new CallbackTransformer(),
'results.stories.records' => $recordTransformer,
'results.stories.records.thumbnail' => $subdefTransformer,
'results.stories.records.technical_informations' => $technicalDataTransformer,
'results.stories.records.subdefs' => $subdefTransformer,
'results.stories.records.metadata' => new CallbackTransformer(),
'results.stories.records.status' => new CallbackTransformer(),
'results.stories.records.caption' => new CallbackTransformer(),
'results.records' => $recordTransformer,
'results.records.thumbnail' => $subdefTransformer,
'results.records.technical_informations' => $technicalDataTransformer,
'results.records.subdefs' => $subdefTransformer,
'results.records.metadata' => new CallbackTransformer(),
'results.records.status' => new CallbackTransformer(),
'results.records.caption' => new CallbackTransformer(),
]);
$includeResolver = new IncludeResolver($transformerResolver);
$fractal = new \League\Fractal\Manager();
$fractal->setSerializer(new TraceableArraySerializer($this->app['dispatcher']));
$fractal->parseIncludes($this->resolveSearchIncludes($request));
$result = $this->doSearch($request);
$story_max_records = null;
// if search on story
if ($request->get('search_type') == 1) {
$story_max_records = (int)$request->get('story_max_records') ?: 10;
}
$searchView = $this->buildSearchView(
$result,
$includeResolver->resolve($fractal),
$this->resolveSubdefUrlTTL($request),
$story_max_records
);
$ret = $fractal->createData(new Item($searchView, $searchTransformer))->toArray();
return Result::create($request, $ret)->createResponse();
}
/**
* Retrieve detailed information about one story
*
* @param Request $request
* @param \record_adapter $story
* @return array
* @throws \Exception
*/
private function listStory(Request $request, \record_adapter $story)
{
if (!$story->isStory()) {
return Result::createError($request, 404, 'Story not found')->createResponse();
}
$per_page = (int)$request->get('per_page')?:10;
$page = (int)$request->get('page')?:1;
$offset = ($per_page * ($page - 1)) + 1;
$caption = $story->get_caption();
$format = function (\caption_record $caption, $dcField) {
$field = $caption->get_dc_field($dcField);
if (!$field) {
return null;
}
return $field->get_serialized_values();
};
return [
'@entity@' => V1Controller::OBJECT_TYPE_STORY,
'databox_id' => $story->getDataboxId(),
'story_id' => $story->getRecordId(),
'updated_on' => $story->getUpdated()->format(DATE_ATOM),
'created_on' => $story->getCreated()->format(DATE_ATOM),
'collection_id' => $story->getCollectionId(),
'base_id' => $story->getBaseId(),
'thumbnail' => $this->listEmbeddableMedia($request, $story, $story->get_thumbnail()),
'uuid' => $story->getUuid(),
'metadatas' => [
'@entity@' => V1Controller::OBJECT_TYPE_STORY_METADATA_BAG,
'dc:contributor' => $format($caption, \databox_Field_DCESAbstract::Contributor),
'dc:coverage' => $format($caption, \databox_Field_DCESAbstract::Coverage),
'dc:creator' => $format($caption, \databox_Field_DCESAbstract::Creator),
'dc:date' => $format($caption, \databox_Field_DCESAbstract::Date),
'dc:description' => $format($caption, \databox_Field_DCESAbstract::Description),
'dc:format' => $format($caption, \databox_Field_DCESAbstract::Format),
'dc:identifier' => $format($caption, \databox_Field_DCESAbstract::Identifier),
'dc:language' => $format($caption, \databox_Field_DCESAbstract::Language),
'dc:publisher' => $format($caption, \databox_Field_DCESAbstract::Publisher),
'dc:relation' => $format($caption, \databox_Field_DCESAbstract::Relation),
'dc:rights' => $format($caption, \databox_Field_DCESAbstract::Rights),
'dc:source' => $format($caption, \databox_Field_DCESAbstract::Source),
'dc:subject' => $format($caption, \databox_Field_DCESAbstract::Subject),
'dc:title' => $format($caption, \databox_Field_DCESAbstract::Title),
'dc:type' => $format($caption, \databox_Field_DCESAbstract::Type),
],
'records' => $this->listRecords($request, array_values($story->getChildren($offset, $per_page)->get_elements())),
];
}
private function listEmbeddableMedia(Request $request, \record_adapter $record, \media_subdef $media)
{
if (!$media->is_physically_present()) {
return null;
}
if ($this->getAuthenticator()->isAuthenticated()) {
$acl = $this->getAclForUser();
if ($media->get_name() !== 'document'
&& false === $acl->has_access_to_subdef($record, $media->get_name())
) {
return null;
}
if ($media->get_name() === 'document'
&& !$acl->has_right_on_base($record->getBaseId(), \ACL::CANDWNLDHD)
&& !$acl->has_hd_grant($record)
) {
return null;
}
}
if ($media->get_permalink() instanceof \media_Permalink_Adapter) {
$permalink = $this->listPermalink($media->get_permalink());
} else {
$permalink = null;
}
$urlTTL = (int) $request->get(
'subdef_url_ttl',
$this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl'])
);
if ($urlTTL < 0) {
$urlTTL = -1;
}
$issuer = $this->getAuthenticatedUser();
return [
'name' => $media->get_name(),
'permalink' => $permalink,
'height' => $media->get_height(),
'width' => $media->get_width(),
'filesize' => $media->get_size(),
'devices' => $media->getDevices(),
'player_type' => $media->get_type(),
'mime_type' => $media->get_mime(),
'substituted' => $media->is_substituted(),
'created_on' => $media->get_creation_date()->format(DATE_ATOM),
'updated_on' => $media->get_modification_date()->format(DATE_ATOM),
'url' => $this->app['media_accessor.subdef_url_generator']->generate($issuer, $media, $urlTTL),
'url_ttl' => $urlTTL,
];
}
private function listPermalink(\media_Permalink_Adapter $permalink)
{
$downloadUrl = $permalink->get_url();
$downloadUrl->getQuery()->set('download', '1');
return [
'created_on' => $permalink->get_created_on()->format(DATE_ATOM),
'id' => $permalink->get_id(),
'is_activated' => $permalink->get_is_activated(),
/** @Ignore */
'label' => $permalink->get_label(),
'updated_on' => $permalink->get_last_modified()->format(DATE_ATOM),
'page_url' => $permalink->get_page(),
'download_url' => (string)$downloadUrl,
'url' => (string)$permalink->get_url(),
];
}
/**
* @param Request $request
* @param RecordReferenceInterface[]|RecordReferenceCollection $records
* @return array
*/
private function listRecords(Request $request, $records)
{
if (!$records instanceof RecordReferenceCollection) {
$records = new RecordReferenceCollection($records);
}
$technicalData = $this->app['service.technical_data']->fetchRecordsTechnicalData($records);
$data = [];
foreach ($records->toRecords($this->getApplicationBox()) as $index => $record) {
$record->setTechnicalDataSet($technicalData[$index]);
$data[$index] = $this->listRecord($request, $record);
}
return $data;
}
/**
* Retrieve detailed information about one record
*
* @param Request $request
* @param \record_adapter $record
* @return array
*/
private function listRecord(Request $request, \record_adapter $record)
{
$technicalInformation = [];
foreach ($record->get_technical_infos()->getValues() as $name => $value) {
$technicalInformation[] = ['name' => $name, 'value' => $value];
}
$data = [
'databox_id' => $record->getDataboxId(),
'record_id' => $record->getRecordId(),
'mime_type' => $record->getMimeType(),
'title' => $record->get_title(),
'original_name' => $record->get_original_name(),
'updated_on' => $record->getUpdated()->format(DATE_ATOM),
'created_on' => $record->getCreated()->format(DATE_ATOM),
'collection_id' => $record->getCollectionId(),
'base_id' => $record->getBaseId(),
'sha256' => $record->getSha256(),
'thumbnail' => $this->listEmbeddableMedia($request, $record, $record->get_thumbnail()),
'technical_informations' => $technicalInformation,
'phrasea_type' => $record->getType(),
'uuid' => $record->getUuid(),
];
if ($request->attributes->get('_extended', false)) {
$data = array_merge($data, [
'subdefs' => $this->listRecordEmbeddableMedias($request, $record),
'metadata' => $this->listRecordMetadata($record),
'status' => $this->listRecordStatus($record),
'caption' => $this->listRecordCaption($record),
]);
}
return $data;
}
/**
* @param Request $request
* @param \record_adapter $record
* @return array
*/
private function listRecordEmbeddableMedias(Request $request, \record_adapter $record)
{
$subdefs = [];
foreach ($record->get_embedable_medias([], []) as $name => $media) {
if (null !== $subdef = $this->listEmbeddableMedia($request, $record, $media)) {
$subdefs[] = $subdef;
}
}
return $subdefs;
}
/**
* List all fields of given record
*
* @param \record_adapter $record
* @return array
*/
private function listRecordMetadata(\record_adapter $record)
{
$includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox());
return $this->listRecordCaptionFields($record->get_caption()->get_fields(null, $includeBusiness));
}
/**
* @param \caption_field[] $fields
* @return array
*/
private function listRecordCaptionFields($fields)
{
$ret = [];
foreach ($fields as $field) {
$databox_field = $field->get_databox_field();
$fieldData = [
'meta_structure_id' => $field->get_meta_struct_id(),
'name' => $field->get_name(),
'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'),
],
];
foreach ($field->get_values() as $value) {
$data = [
'meta_id' => $value->getId(),
'value' => $value->getValue(),
];
$ret[] = $fieldData + $data;
}
}
return $ret;
}
/**
* Retrieve detailed information about one status
*
* @param \record_adapter $record
* @return array
*/
private function listRecordStatus(\record_adapter $record)
{
$ret = [];
foreach ($record->getStatusStructure() as $bit => $status) {
$ret[] = [
'bit' => $bit,
'state' => \databox_status::bitIsSet($record->getStatusBitField(), $bit),
];
}
return $ret;
}
/**
* @param \record_adapter $record
* @return array
*/
private function listRecordCaption(\record_adapter $record)
{
$includeBusiness = $this->getAclForUser()->can_see_business_fields($record->getDatabox());
$caption = [];
foreach ($record->get_caption()->get_fields(null, $includeBusiness) as $field) {
$caption[] = [
'meta_structure_id' => $field->get_meta_struct_id(),
'name' => $field->get_name(),
'value' => $field->get_serialized_values(';'),
];
}
return $caption;
}
/**
* Returns requested includes
*
* @param Request $request
* @return string[]
*/
private function resolveSearchIncludes(Request $request)
{
$includes = [
'results.stories.records'
];
if ($request->attributes->get('_extended', false)) {
if ($request->get('search_type') != SearchEngineOptions::RECORD_STORY) {
$includes = array_merge($includes, [
'results.stories.records.subdefs',
'results.stories.records.metadata',
'results.stories.records.caption',
'results.stories.records.status'
]);
}
else {
$includes = [ 'results.stories.caption' ];
}
$includes = array_merge($includes, [
'results.records.subdefs',
'results.records.metadata',
'results.records.caption',
'results.records.status'
]);
}
return $includes;
}
/**
* @param SearchEngineResult $result
* @param string[] $includes
* @param int $urlTTL
* @param int|null $story_max_records
* @return SearchResultView
*/
private function buildSearchView(SearchEngineResult $result, array $includes, $urlTTL, $story_max_records = null)
{
$references = new RecordReferenceCollection($result->getResults());
$records = new RecordCollection();
$stories = new RecordCollection();
foreach ($references->toRecords($this->getApplicationBox()) as $record) {
if ($record->isStory()) {
$stories[$record->getId()] = $record;
} else {
$records[$record->getId()] = $record;
}
}
$resultView = new SearchResultView($result);
if ($stories->count() > 0) {
$user = $this->getAuthenticatedUser();
$children = [];
foreach ($stories->getDataboxIds() as $databoxId) {
$storyIds = $stories->getDataboxRecordIds($databoxId);
$selections = $this->findDataboxById($databoxId)
->getRecordRepository()
->findChildren($storyIds, $user,1, $story_max_records);
$children[$databoxId] = array_combine($storyIds, $selections);
}
/** @var StoryView[] $storyViews */
$storyViews = [];
/** @var RecordView[] $childrenViews */
$childrenViews = [];
foreach ($stories as $index => $story) {
$storyView = new StoryView($story);
$selection = $children[$story->getDataboxId()][$story->getRecordId()];
$childrenView = $this->buildRecordViews($selection);
foreach ($childrenView as $view) {
$childrenViews[spl_object_hash($view)] = $view;
}
$storyView->setChildren($childrenView);
$storyViews[$index] = $storyView;
}
if (in_array('results.stories.thumbnail', $includes, true)) {
$subdefViews = $this->buildSubdefsViews($stories, ['thumbnail'], $urlTTL);
foreach ($storyViews as $index => $storyView) {
$storyView->setSubdefs($subdefViews[$index]);
}
}
if (in_array('results.stories.metadatas', $includes, true) ||
in_array('results.stories.caption', $includes, true)) {
$captions = $this->app['service.caption']->findByReferenceCollection($stories);
$canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($stories);
$this->buildCaptionViews($storyViews, $captions, $canSeeBusiness);
}
$allChildren = new RecordCollection();
foreach ($childrenViews as $index => $childrenView) {
$allChildren[$index] = $childrenView->getRecord();
}
$names = in_array('results.stories.records.subdefs', $includes, true) ? null : ['thumbnail'];
$subdefViews = $this->buildSubdefsViews($allChildren, $names, $urlTTL);
$technicalDatasets = $this->app['service.technical_data']->fetchRecordsTechnicalData($allChildren);
foreach ($childrenViews as $index => $recordView) {
$recordView->setSubdefs($subdefViews[$index]);
$recordView->setTechnicalDataView(new TechnicalDataView($technicalDatasets[$index]));
}
if (array_intersect($includes, ['results.stories.records.metadata', 'results.stories.records.caption'])) {
$captions = $this->app['service.caption']->findByReferenceCollection($allChildren);
$canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($allChildren);
$this->buildCaptionViews($childrenViews, $captions, $canSeeBusiness);
}
$resultView->setStories($storyViews);
}
if ($records->count() > 0) {
$names = in_array('results.records.subdefs', $includes, true) ? null : ['thumbnail'];
$recordViews = $this->buildRecordViews($records);
$subdefViews = $this->buildSubdefsViews($records, $names, $urlTTL);
$technicalDatasets = $this->app['service.technical_data']->fetchRecordsTechnicalData($records);
foreach ($recordViews as $index => $recordView) {
$recordView->setSubdefs($subdefViews[$index]);
$recordView->setTechnicalDataView(new TechnicalDataView($technicalDatasets[$index]));
}
if (array_intersect($includes, ['results.records.metadata', 'results.records.caption'])) {
$captions = $this->app['service.caption']->findByReferenceCollection($records);
$canSeeBusiness = $this->retrieveSeeBusinessPerDatabox($records);
$this->buildCaptionViews($recordViews, $captions, $canSeeBusiness);
}
$resultView->setRecords($recordViews);
}
return $resultView;
}
/**
* @param Request $request
* @return SearchEngineResult
*/
private function doSearch(Request $request)
{
$options = SearchEngineOptions::fromRequest($this->app, $request);
$options->setFirstResult((int)($request->get('offset_start') ?: 0));
$options->setMaxResults((int)$request->get('per_page') ?: 10);
$this->getSearchEngine()->resetCache();
$search_result = $this->getSearchEngine()->query((string)$request->get('query'), $options);
$this->getUserManipulator()->logQuery($this->getAuthenticatedUser(), $search_result->getQueryText());
// log array of collectionIds (from $options) for each databox
$collectionsReferencesByDatabox = $options->getCollectionsReferencesByDatabox();
foreach ($collectionsReferencesByDatabox as $sbid => $references) {
$databox = $this->findDataboxById($sbid);
$collectionsIds = array_map(function(CollectionReference $ref){return $ref->getCollectionId();}, $references);
$this->getSearchEngineLogger()->log($databox, $search_result->getQueryText(), $search_result->getTotal(), $collectionsIds);
}
$this->getSearchEngine()->clearCache();
return $search_result;
}
/**
* @return SearchEngineInterface
*/
private function getSearchEngine()
{
return $this->app['phraseanet.SE'];
}
/**
* @return UserManipulator
*/
private function getUserManipulator()
{
return $this->app['manipulator.user'];
}
/**
* @return SearchEngineLogger
*/
private function getSearchEngineLogger()
{
return $this->app['phraseanet.SE.logger'];
}
/**
* @param Request $request
* @return int
*/
private function resolveSubdefUrlTTL(Request $request)
{
$urlTTL = $request->query->get('subdef_url_ttl');
if (null !== $urlTTL) {
return (int)$urlTTL;
}
return $this->getConf()->get(['registry', 'general', 'default-subdef-url-ttl']);
}
/**
* @param RecordCollection|\record_adapter[] $references
* @return RecordView[]
*/
private function buildRecordViews($references)
{
if (!$references instanceof RecordCollection) {
$references = new RecordCollection($references);
}
$recordViews = [];
foreach ($references as $index => $record) {
$recordViews[$index] = new RecordView($record);
}
return $recordViews;
}
/**
* @param RecordReferenceInterface[]|RecordReferenceCollection|DataboxGroupable $references
* @param array|null $names
* @param int $urlTTL
* @return SubdefView[][]
*/
private function buildSubdefsViews($references, array $names = null, $urlTTL)
{
$subdefGroups = $this->app['service.media_subdef']
->findSubdefsByRecordReferenceFromCollection($references, $names);
$fakeSubdefs = [];
foreach ($subdefGroups as $index => $subdefGroup) {
if (!isset($subdefGroup['thumbnail'])) {
$fakeSubdef = new \media_subdef($this->app, $references[$index], 'thumbnail', true, []);
$fakeSubdefs[spl_object_hash($fakeSubdef)] = $fakeSubdef;
$subdefGroups[$index]['thumbnail'] = $fakeSubdef;
}
}
$allSubdefs = $this->mergeGroupsIntoOneList($subdefGroups);
$allPermalinks = \media_Permalink_Adapter::getMany(
$this->app,
array_filter($allSubdefs, function (\media_subdef $subdef) use ($fakeSubdefs) {
return !isset($fakeSubdefs[spl_object_hash($subdef)]);
})
);
$urls = $this->app['media_accessor.subdef_url_generator']
->generateMany($this->getAuthenticatedUser(), $allSubdefs, $urlTTL);
$subdefViews = [];
/** @var \media_subdef $subdef */
foreach ($allSubdefs as $index => $subdef) {
$subdefView = new SubdefView($subdef);
if (isset($allPermalinks[$index])) {
$subdefView->setPermalinkView(new PermalinkView($allPermalinks[$index]));
}
$subdefView->setUrl($urls[$index]);
$subdefView->setUrlTTL($urlTTL);
$subdefViews[spl_object_hash($subdef)] = $subdefView;
}
$reorderedGroups = [];
/** @var \media_subdef[] $subdefGroup */
foreach ($subdefGroups as $index => $subdefGroup) {
$reordered = [];
foreach ($subdefGroup as $subdef) {
$reordered[] = $subdefViews[spl_object_hash($subdef)];
}
$reorderedGroups[$index] = $reordered;
}
return $reorderedGroups;
}
/**
* @param array $groups
* @return array|mixed
*/
private function mergeGroupsIntoOneList(array $groups)
{
// Strips keys from the internal array
array_walk($groups, function (array &$group) {
$group = array_values($group);
});
if ($groups) {
return call_user_func_array('array_merge', $groups);
}
return [];
}
/**
* @param RecordReferenceInterface[]|DataboxGroupable $references
* @return array<int, bool>
*/
private function retrieveSeeBusinessPerDatabox($references)
{
if (!$references instanceof DataboxGroupable) {
$references = new RecordReferenceCollection($references);
}
$acl = $this->getAclForUser();
$canSeeBusiness = [];
foreach ($references->getDataboxIds() as $databoxId) {
$canSeeBusiness[$databoxId] = $acl->can_see_business_fields($this->findDataboxById($databoxId));
}
$rights = [];
foreach ($references as $index => $reference) {
$rights[$index] = $canSeeBusiness[$reference->getDataboxId()];
}
return $rights;
}
/**
* @param RecordView[] $recordViews
* @param \caption_record[] $captions
* @param bool[] $canSeeBusiness
*/
private function buildCaptionViews($recordViews, $captions, $canSeeBusiness)
{
foreach ($recordViews as $index => $recordView) {
$caption = $captions[$index];
$captionView = new CaptionView($caption);
$captionView->setFields($caption->get_fields(null, isset($canSeeBusiness[$index]) && (bool)$canSeeBusiness[$index]));
$recordView->setCaption($captionView);
}
}
}

View File

@@ -19,8 +19,10 @@ use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Feed\Aggregate; use Alchemy\Phrasea\Feed\Aggregate;
use Alchemy\Phrasea\Feed\Link\AggregateLinkGenerator; use Alchemy\Phrasea\Feed\Link\AggregateLinkGenerator;
use Alchemy\Phrasea\Feed\Link\FeedLinkGenerator; use Alchemy\Phrasea\Feed\Link\FeedLinkGenerator;
use Alchemy\Phrasea\Model\Entities\Feed;
use Alchemy\Phrasea\Model\Entities\FeedEntry; use Alchemy\Phrasea\Model\Entities\FeedEntry;
use Alchemy\Phrasea\Model\Entities\FeedItem; use Alchemy\Phrasea\Model\Entities\FeedItem;
use Alchemy\Phrasea\Model\Entities\FeedPublisher;
use Alchemy\Phrasea\Model\Repositories\FeedEntryRepository; use Alchemy\Phrasea\Model\Repositories\FeedEntryRepository;
use Alchemy\Phrasea\Model\Repositories\FeedItemRepository; use Alchemy\Phrasea\Model\Repositories\FeedItemRepository;
use Alchemy\Phrasea\Model\Repositories\FeedPublisherRepository; use Alchemy\Phrasea\Model\Repositories\FeedPublisherRepository;
@@ -46,6 +48,7 @@ class FeedController extends Controller
} }
public function createFeedEntryAction(Request $request) { public function createFeedEntryAction(Request $request) {
/** @var Feed $feed */
$feed = $this->getFeedRepository()->find($request->request->get('feed_id')); $feed = $this->getFeedRepository()->find($request->request->get('feed_id'));
if (null === $feed) { if (null === $feed) {
@@ -53,6 +56,8 @@ class FeedController extends Controller
} }
$user = $this->getAuthenticatedUser(); $user = $this->getAuthenticatedUser();
/** @var FeedPublisher $publisher */
$publisher = $this->getFeedPublisherRepository()->findOneBy([ $publisher = $this->getFeedPublisherRepository()->findOneBy([
'feed' => $feed, 'feed' => $feed,
'user' => $user, 'user' => $user,

View File

@@ -136,6 +136,7 @@ class LanguageController
'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'),
'Save as VTT' => $translator->trans('Save as VTT'),
'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'),

View File

@@ -12,7 +12,7 @@ namespace Alchemy\Phrasea\Controller\Prod;
use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware; use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware;
use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Helper\Record as RecordHelper; use Alchemy\Phrasea\Helper\Record as RecordHelper;
use Alchemy\Phrasea\Out\Module\PDF as PDFExport; use Alchemy\Phrasea\Out\Module\PDFRecords;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@@ -24,19 +24,27 @@ class PrinterController extends Controller
{ {
$printer = new RecordHelper\Printer($this->app, $request); $printer = new RecordHelper\Printer($this->app, $request);
return $this->render('prod/actions/printer_default.html.twig', ['printer' => $printer, 'message' => '']); $basketFeedbackId = null;
if($printer->is_basket() && ($basket = $printer->get_original_basket()) && ($validation = $basket->getValidation())) {
if($validation->getInitiator()->getId() === $this->app->getAuthenticatedUser()->getId()) {
$basketFeedbackId = $basket->getId();
}
}
return $this->render('prod/actions/printer_default.html.twig', ['printer' => $printer, 'message' => '', 'basketFeedbackId' => $basketFeedbackId]);
} }
public function printAction(Request $request) public function printAction(Request $request)
{ {
$printer = new RecordHelper\Printer($this->app, $request); $printer = new RecordHelper\Printer($this->app, $request);
$b = $printer->get_original_basket();
$layout = $request->request->get('lay'); $layout = $request->request->get('lay');
foreach ($printer->get_elements() as $record) { foreach ($printer->get_elements() as $record) {
$this->getDataboxLogger($record->getDatabox())->log($record, \Session_Logger::EVENT_PRINT, $layout, ''); $this->getDataboxLogger($record->getDatabox())->log($record, \Session_Logger::EVENT_PRINT, $layout, '');
} }
$PDF = new PDFExport($this->app, $printer->get_elements(), $layout); $PDF = new PDFRecords($this->app, $printer, $layout);
$response = new Response($PDF->render(), 200, array('Content-Type' => 'application/pdf')); $response = new Response($PDF->render(), 200, array('Content-Type' => 'application/pdf'));
$response->headers->set('Pragma', 'public', true); $response->headers->set('Pragma', 'public', true);
@@ -44,4 +52,5 @@ class PrinterController extends Controller
return $response; return $response;
} }
} }

View File

@@ -433,24 +433,15 @@ class QueryController extends Controller
// populates facets (aggregates) // populates facets (aggregates)
$facets = []; $facets = [];
// $facetClauses = [];
foreach ($result->getFacets() as $facet) { foreach ($result->getFacets() as $facet) {
$facetName = $facet['name']; $facetName = $facet['name'];
if(array_key_exists($facetName, $fieldsInfosByName)) { if(array_key_exists($facetName, $fieldsInfosByName)) {
$f = $fieldsInfosByName[$facetName]; $f = $fieldsInfosByName[$facetName];
$facet['label'] = $f['trans_label']; $facet['label'] = $f['trans_label'];
$facet['labels'] = $f['labels']; $facet['labels'] = $f['labels'];
$facet['type'] = strtoupper($f['type']) . "-AGGREGATE"; $facet['type'] = strtoupper($f['type']) . "-AGGREGATE";
$facets[] = $facet; $facets[] = $facet;
// $facetClauses[] = [
// 'type' => strtoupper($f['type']) . "-AGGREGATE",
// 'field' => $f['field'],
// 'facet' => $facet
// ];
} }
} }

View File

@@ -458,9 +458,9 @@ class AccountController extends Controller
->setZipCode($request->request->get("form_zip")) ->setZipCode($request->request->get("form_zip"))
->setPhone($request->request->get("form_phone")) ->setPhone($request->request->get("form_phone"))
->setFax($request->request->get("form_fax")) ->setFax($request->request->get("form_fax"))
->setJob($request->request->get("form_activity")) ->setJob($request->request->get("form_function"))
->setCompany($request->request->get("form_company")) ->setCompany($request->request->get("form_company"))
->setPosition($request->request->get("form_function")) ->setPosition($request->request->get("form_activity"))
->setNotifications((Boolean) $request->request->get("mail_notifications")); ->setNotifications((Boolean) $request->request->get("mail_notifications"));
$service->updateAccount($command); $service->updateAccount($command);

View File

@@ -0,0 +1,48 @@
<?php
namespace Alchemy\Phrasea\ControllerProvider\Api;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Controller\Api\V3Controller;
use Alchemy\Phrasea\Core\Event\Listener\OAuthListener;
use Silex\Application;
use Silex\ControllerCollection;
use Silex\ControllerProviderInterface;
use Silex\ServiceProviderInterface;
class V3 extends Api implements ControllerProviderInterface, ServiceProviderInterface
{
const VERSION = '3.0.0';
public function register(Application $app)
{
$app['controller.api.v3'] = $app->share(function (PhraseaApplication $app) {
return (new V3Controller($app));
});
}
public function boot(Application $app)
{
}
public function connect(Application $app)
{
if (! $this->isApiEnabled($app)) {
return $app['controllers_factory'];
}
/** @var ControllerCollection $controllers */
$controllers = $app['controllers_factory'];
$controllers->before(new OAuthListener());
$controllers->get('/stories/{databox_id}/{record_id}/', 'controller.api.v3:getStoryAction')
->before('controller.api.v1:ensureCanAccessToRecord')
->assert('databox_id', '\d+')
->assert('record_id', '\d+');
$controllers->match('/search/', 'controller.api.v3:searchAction');
return $controllers;
}
}

View File

@@ -20,6 +20,7 @@ class DisplaySettingService
const ORDER_BY_ADMIN = "ORDER_BY_ADMIN"; const ORDER_BY_ADMIN = "ORDER_BY_ADMIN";
const ORDER_BY_BCT = "ORDER_BY_BCT"; const ORDER_BY_BCT = "ORDER_BY_BCT";
const ORDER_BY_HITS = "ORDER_BY_HITS"; const ORDER_BY_HITS = "ORDER_BY_HITS";
const ORDER_BY_HITS_ASC = "ORDER_BY_HITS_ASC";
/** /**
* The default user settings. * The default user settings.
@@ -31,7 +32,7 @@ class DisplaySettingService
'images_per_page' => '20', 'images_per_page' => '20',
'images_size' => '120', 'images_size' => '120',
'editing_images_size' => '134', 'editing_images_size' => '134',
'editing_top_box' => '180px', 'editing_top_box' => '120px',
'editing_right_box' => '400px', 'editing_right_box' => '400px',
'editing_left_box' => '710px', 'editing_left_box' => '710px',
'basket_sort_field' => 'name', 'basket_sort_field' => 'name',

View File

@@ -15,6 +15,7 @@ use Alchemy\Phrasea\Authentication\Context;
use Alchemy\Phrasea\Controller\Api\Result; use Alchemy\Phrasea\Controller\Api\Result;
use Alchemy\Phrasea\ControllerProvider\Api\V1; use Alchemy\Phrasea\ControllerProvider\Api\V1;
use Alchemy\Phrasea\ControllerProvider\Api\V2; use Alchemy\Phrasea\ControllerProvider\Api\V2;
use Alchemy\Phrasea\ControllerProvider\Api\V3;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\Core\Event\ApiOAuth2EndEvent; use Alchemy\Phrasea\Core\Event\ApiOAuth2EndEvent;
use Alchemy\Phrasea\Core\Event\ApiOAuth2StartEvent; use Alchemy\Phrasea\Core\Event\ApiOAuth2StartEvent;
@@ -76,11 +77,13 @@ class OAuthListener
// Sets the Api Version // Sets the Api Version
$CalledController = $request->attributes->get('_controller'); $CalledController = $request->attributes->get('_controller');
if(mb_strpos($CalledController, 'controller.api.v1') !== FALSE){ if (mb_strpos($CalledController, 'controller.api.v1') !== FALSE) {
$request->attributes->set('api_version', V1::VERSION); $request->attributes->set('api_version', V1::VERSION);
}elseif(mb_strpos($CalledController, 'controller.api.v2') !== FALSE){ } elseif(mb_strpos($CalledController, 'controller.api.v2') !== FALSE) {
$request->attributes->set('api_version', V2::VERSION); $request->attributes->set('api_version', V2::VERSION);
}else{ } elseif(mb_strpos($CalledController, 'controller.api.v3') !== FALSE) {
$request->attributes->set('api_version', V3::VERSION);
} else {
$request->attributes->set('api_version', $oAuth2Account->getApiVersion()); $request->attributes->set('api_version', $oAuth2Account->getApiVersion());
} }

View File

@@ -13,7 +13,9 @@ namespace Alchemy\Phrasea\Core\Event\Subscriber;
use Alchemy\Phrasea\Core\Event\FeedEntryEvent; use Alchemy\Phrasea\Core\Event\FeedEntryEvent;
use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Core\PhraseaEvents;
use Alchemy\Phrasea\Model\Entities\User;
use Alchemy\Phrasea\Model\Entities\WebhookEvent; use Alchemy\Phrasea\Model\Entities\WebhookEvent;
use Alchemy\Phrasea\Model\Manipulator\TokenManipulator;
use Alchemy\Phrasea\Notification\Receiver; use Alchemy\Phrasea\Notification\Receiver;
use Alchemy\Phrasea\Notification\Mail\MailInfoNewPublication; use Alchemy\Phrasea\Notification\Mail\MailInfoNewPublication;
@@ -53,36 +55,43 @@ class FeedEntrySubscriber extends AbstractNotificationSubscriber
do { do {
$results = $Query->limit($start, $perLoop)->execute()->get_results(); $results = $Query->limit($start, $perLoop)->execute()->get_results();
foreach ($results as $user_to_notif) { $users_emailed = []; // for all users
$mailed = false; $users_to_email = []; // list only users who must be emailed (=create tokens)
if ($params['notify_email'] && $this->shouldSendNotificationFor($user_to_notif, 'eventsmanager_notify_feed')) { /** @var User $user */
$readyToSend = false; foreach ($results as $user) {
try { $users_emailed[$user->getId()] = false;
$token = $this->app['manipulator.token']->createFeedEntryToken($user_to_notif, $entry); if ($params['notify_email'] && $this->shouldSendNotificationFor($user, 'eventsmanager_notify_feed')) {
$url = $this->app->url('lightbox', ['LOG' => $token->getValue()]); $users_to_email[$user->getId()] = $user;
$receiver = Receiver::fromUser($user_to_notif);
$readyToSend = true;
} catch (\Exception $e) {
}
if ($readyToSend) {
$mail = MailInfoNewPublication::create($this->app, $receiver);
$mail->setButtonUrl($url);
$mail->setAuthor($entry->getAuthorName());
$mail->setTitle($entry->getTitle());
$this->deliver($mail);
$mailed = true;
}
} }
$this->app['events-manager']->notify($user_to_notif->getId(), 'eventsmanager_notify_feed', $datas, $mailed);
} }
// get many tokens in one shot
$tokens = $this->getTokenManipulator()->createFeedEntryTokens($users_to_email, $entry);
foreach($tokens as $token) {
try {
$url = $this->app->url('lightbox', ['LOG' => $token->getValue()]);
$receiver = Receiver::fromUser($token->getUser());
$mail = MailInfoNewPublication::create($this->app, $receiver);
$mail->setButtonUrl($url);
$mail->setAuthor($entry->getAuthorName());
$mail->setTitle($entry->getTitle());
$this->deliver($mail);
$users_emailed[$token->getUser()->getId()] = true;
}
catch (\Exception $e) {
// no-op
}
}
foreach($users_emailed as $id => $emailed) {
$this->app['events-manager']->notify($id, 'eventsmanager_notify_feed', $datas, $emailed);
}
$start += $perLoop; $start += $perLoop;
} while (count($results) > 0); }
while (count($results) > 0);
} }
public static function getSubscribedEvents() public static function getSubscribedEvents()
@@ -91,4 +100,12 @@ class FeedEntrySubscriber extends AbstractNotificationSubscriber
PhraseaEvents::FEED_ENTRY_CREATE => 'onCreate', PhraseaEvents::FEED_ENTRY_CREATE => 'onCreate',
]; ];
} }
/**
* @return TokenManipulator
*/
private function getTokenManipulator()
{
return $this->app['manipulator.token'];
}
} }

View File

@@ -9,6 +9,8 @@ use MediaVorus\MediaVorusServiceProvider;
use MP4Box\MP4BoxServiceProvider; use MP4Box\MP4BoxServiceProvider;
use Neutron\Silex\Provider\ImagineServiceProvider; use Neutron\Silex\Provider\ImagineServiceProvider;
use PHPExiftool\PHPExiftoolServiceProvider; use PHPExiftool\PHPExiftoolServiceProvider;
use PHPExiftool\Reader;
use PHPExiftool\Writer;
use Silex\Application; use Silex\Application;
use Silex\ServiceProviderInterface; use Silex\ServiceProviderInterface;
@@ -48,6 +50,21 @@ class MediaUtilitiesMetaServiceProvider implements ServiceProviderInterface
public function boot(Application $app) public function boot(Application $app)
{ {
// no-op if(isset($app['exiftool.reader']) && isset($app['conf'])) {
try {
$timeout = $app['conf']->get(['main', 'binaries', 'exiftool_timeout'], 60);
/** @var Reader $exiftoolReader */
$exiftoolReader = $app['exiftool.reader'];
$exiftoolReader->setTimeout($timeout);
/** @var Writer $exiftoolWriter */
$exiftoolWriter = $app['exiftool.writer'];
$exiftoolWriter->setTimeout($timeout);
}
catch(\Exception $e) {
// no-nop
}
}
} }
} }

View File

@@ -97,7 +97,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
}); });
$app['elasticsearch.facets_response.factory'] = $app->protect(function (array $response) use ($app) { $app['elasticsearch.facets_response.factory'] = $app->protect(function (array $response) use ($app) {
return new FacetsResponse(new Escaper(), $response, $app['search_engine.structure']); return new FacetsResponse($app['elasticsearch.options'], new Escaper(), $response, $app['search_engine.structure']);
}); });
return $app; return $app;
@@ -228,7 +228,8 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
}); });
$app['elasticsearch.options'] = $app->share(function ($app) { $app['elasticsearch.options'] = $app->share(function ($app) {
$options = ElasticsearchOptions::fromArray($app['conf']->get(['main', 'search-engine', 'options'], [])); $conf = $app['conf']->get(['main', 'search-engine', 'options'], []);
$options = ElasticsearchOptions::fromArray($conf);
if (empty($options->getIndexName())) { if (empty($options->getIndexName())) {
$options->setIndexName(strtolower(sprintf('phraseanet_%s', str_replace( $options->setIndexName(strtolower(sprintf('phraseanet_%s', str_replace(

View File

@@ -16,7 +16,8 @@ class Version
/** /**
* @var string * @var string
*/ */
private $number = '4.1.0-alpha.19a';
private $number = '4.1.0-alpha.23a';
/** /**
* @var string * @var string

View File

@@ -182,7 +182,7 @@ class LegacyRecordRepository implements RecordRepository
return $this->mapRecordsFromResultSet($result); return $this->mapRecordsFromResultSet($result);
} }
public function findChildren(array $storyIds, $user = null) public function findChildren(array $storyIds, $user = null, $offset = 1, $max_items = null)
{ {
if (!$storyIds) { if (!$storyIds) {
return []; return [];
@@ -191,26 +191,73 @@ class LegacyRecordRepository implements RecordRepository
$connection = $this->databox->get_connection(); $connection = $this->databox->get_connection();
$selects = $this->getRecordSelects(); $selects = $this->getRecordSelects();
array_unshift($selects, 's.rid_parent as story_id');
$builder = $connection->createQueryBuilder(); if ($max_items) {
$builder array_unshift($selects, 'sr.rid_parent as story_id');
->select($selects)
->from('regroup', 's')
->innerJoin('s', 'record', 'r', 'r.record_id = s.rid_child')
->where(
's.rid_parent IN (:storyIds)',
'r.parent_record_id = 0'
)
->orderBy('s.ord', 'ASC')
->setParameter('storyIds', $storyIds, Connection::PARAM_INT_ARRAY)
;
if (null !== $user) { $subBuilder = $connection->createQueryBuilder();
$this->addUserFilter($builder, $user);
$subBuilder
->select('s.*,
IF(@old_rid_parent != s.rid_parent, @cpt := 1, @cpt := @cpt+1) AS CPT')
->addSelect("IF(@old_rid_parent != s.rid_parent, IF(@old_rid_parent:=s.rid_parent,'NEW PARENT',0), '----------') AS Y")
->from('regroup', 's')
->where('s.rid_parent IN (:storyIds)')
->setParameter('storyIds', $storyIds, Connection::PARAM_INT_ARRAY)
->orderBy('s.rid_parent, s.ord')
;
$builder = $subBuilder->getConnection()->createQueryBuilder();
$builder->select($selects)
->from(sprintf('( %s )', $subBuilder->getSQL()), 'sr')
->innerJoin('sr', 'record', 'r', 'r.record_id = sr.rid_child')
->where('sr.CPT BETWEEN :offset AND :maxresult')
->andWhere('r.parent_record_id = 0')
->setParameter('offset', $offset)
->setParameter('maxresult', ($offset + $max_items -1))
->orderBy('story_id, sr.CPT')
;
if (null !== $user) {
$this->addUserFilter($builder, $user);
}
$connection->executeQuery('SET @cpt = 1');
$connection->executeQuery('SET @old_rid_parent = -1');
$data = $connection->fetchAll(
$builder->getSQL(),
array_merge($subBuilder->getParameters(), $builder->getParameters()),
array_merge($subBuilder->getParameterTypes(), $builder->getParameterTypes())
);
} else {
array_unshift($selects, 's.rid_parent as story_id');
$builder = $connection->createQueryBuilder();
$builder
->select($selects)
->from('regroup', 's')
->innerJoin('s', 'record', 'r', 'r.record_id = s.rid_child')
->where(
's.rid_parent IN (:storyIds)',
'r.parent_record_id = 0'
)
->orderBy('s.ord', 'ASC')
->setParameter('storyIds', $storyIds, Connection::PARAM_INT_ARRAY)
;
if (null !== $user) {
$this->addUserFilter($builder, $user);
}
$data = $connection->fetchAll($builder->getSQL(), $builder->getParameters(), $builder->getParameterTypes());
} }
$data = $connection->fetchAll($builder->getSQL(), $builder->getParameters(), $builder->getParameterTypes());
$records = $this->mapRecordsFromResultSet($data); $records = $this->mapRecordsFromResultSet($data);
$selections = array_map( $selections = array_map(

View File

@@ -57,9 +57,11 @@ interface RecordRepository
* *
* @param int[] $storyIds * @param int[] $storyIds
* @param null|int|User $user * @param null|int|User $user
* @param int $offset
* @param null|int $max_items
* @return \set_selection[] * @return \set_selection[]
*/ */
public function findChildren(array $storyIds, $user = null); public function findChildren(array $storyIds, $user = null, $offset = 1, $max_items = null);
/** /**

View File

@@ -657,8 +657,8 @@ class Edit extends \Alchemy\Phrasea\Helper\Helper
->setEmail($parm['email']) ->setEmail($parm['email'])
->setAddress($parm['address']) ->setAddress($parm['address'])
->setZipCode($parm['zip']) ->setZipCode($parm['zip'])
->setActivity($parm['function']) ->setActivity($parm['activite'])
->setJob($parm['activite']) ->setJob($parm['function'])
->setCompany($parm['company']) ->setCompany($parm['company'])
->setPhone($parm['telephone']) ->setPhone($parm['telephone'])
->setFax($parm['fax']); ->setFax($parm['fax']);

View File

@@ -456,17 +456,27 @@ class Basket
} }
public function hasRecord(Application $app, \record_adapter $record) public function hasRecord(Application $app, \record_adapter $record)
{
return !is_null($this->getElementByRecord($app, $record));
}
/**
* @param Application $app
* @param \record_adapter $record
* @return BasketElement
*/
public function getElementByRecord(Application $app, \record_adapter $record)
{ {
foreach ($this->getElements() as $basket_element) { foreach ($this->getElements() as $basket_element) {
$bask_record = $basket_element->getRecord($app); $bask_record = $basket_element->getRecord($app);
if ($bask_record->getRecordId() == $record->getRecordId() if ($bask_record->getRecordId() == $record->getRecordId()
&& $bask_record->getDataboxId() == $record->getDataboxId()) { && $bask_record->getDataboxId() == $record->getDataboxId()) {
return true; return $basket_element;
} }
} }
return false; return null;
} }
public function getSize(Application $app) public function getSize(Application $app)

View File

@@ -15,7 +15,14 @@ use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo; use Gedmo\Mapping\Annotation as Gedmo;
/** /**
* @ORM\Table(name="Tokens") * @ORM\Table(name="Tokens",
* indexes={
* @ORM\index(name="type", columns={"type"}),
* @ORM\index(name="created", columns={"created"}),
* @ORM\index(name="updated", columns={"updated"}),
* @ORM\index(name="expiration", columns={"expiration"})
* }
* )
* @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\TokenRepository") * @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\TokenRepository")
*/ */
class Token class Token

View File

@@ -30,7 +30,7 @@ class WebhookEventPayload
private $id; private $id;
/** /**
* @ORM\OneToOne(targetEntity="WebhookEventDelivery") * @ORM\OneToOne(targetEntity="WebhookEventDelivery", inversedBy="payload")
* @ORM\JoinColumn(name="delivery_id", referencedColumnName="id") * @ORM\JoinColumn(name="delivery_id", referencedColumnName="id")
*/ */
private $delivery; private $delivery;

View File

@@ -22,7 +22,6 @@ use RandomLib\Generator;
class TokenManipulator implements ManipulatorInterface class TokenManipulator implements ManipulatorInterface
{ {
const LETTERS_AND_NUMBERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const LETTERS_AND_NUMBERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const TYPE_FEED_ENTRY = 'FEED_ENTRY'; const TYPE_FEED_ENTRY = 'FEED_ENTRY';
const TYPE_PASSWORD = 'password'; const TYPE_PASSWORD = 'password';
const TYPE_ACCOUNT_UNLOCK = 'account-unlock'; const TYPE_ACCOUNT_UNLOCK = 'account-unlock';
@@ -126,6 +125,38 @@ class TokenManipulator implements ManipulatorInterface
return $this->create($user, self::TYPE_FEED_ENTRY, null, $entry->getId()); return $this->create($user, self::TYPE_FEED_ENTRY, null, $entry->getId());
} }
/**
* Create feedEntryTokens for many users in one shot
*
* @param User[] $users
* @param FeedEntry $entry
* @return Token[]
* @throws \Doctrine\DBAL\DBALException
*/
public function createFeedEntryTokens($users, FeedEntry $entry)
{
// $this->removeExpiredTokens();
$tokens = [];
foreach ($users as $user) {
$value = $this->random->generateString(32, self::LETTERS_AND_NUMBERS) . $user->getId();
$token = new Token();
$token->setUser($user)
->setType(self::TYPE_FEED_ENTRY)
->setValue($value)
->setExpiration(null)
->setData($entry->getId());
$tokens[] = $token;
$this->om->persist($token);
}
$this->om->flush();
$this->om->clear();
return $tokens;
}
/** /**
* @param User $user * @param User $user
* @param $data * @param $data

View File

@@ -368,7 +368,7 @@ class UserManipulator implements ManipulatorInterface
throw new InvalidArgumentException(sprintf('Email %s is not legal.', $email)); throw new InvalidArgumentException(sprintf('Email %s is not legal.', $email));
} }
if (null !== $this->repository->findByEmail($email)) { if (($email !== null) && (null !== $this->repository->findByEmail($email))) {
throw new RuntimeException(sprintf('User with email %s already exists.', $email)); throw new RuntimeException(sprintf('User with email %s already exists.', $email));
} }

View File

@@ -232,7 +232,7 @@ class BasketRepository extends EntityRepository
$dql = "SELECT b\n" $dql = "SELECT b\n"
. "FROM Phraseanet:Basket b\n" . "FROM Phraseanet:Basket b\n"
. " JOIN b.elements e\n" . " JOIN b.elements e\n"
. "WHERE b.user = :usr_id AND b.pusher_id IS NOT NULL"; . "WHERE b.user = :usr_id AND b.pusher IS NOT NULL";
$params = [ $params = [
'usr_id' => $user->getId() 'usr_id' => $user->getId()
]; ];

View File

@@ -72,4 +72,9 @@ class TokenRepository extends EntityRepository
return $query->getResult(); return $query->getResult();
} }
public function getEntityManager()
{
return parent::getEntityManager();
}
} }

View File

@@ -122,4 +122,15 @@ class UserRepository extends EntityRepository
{ {
return $this->findBy(['templateOwner' => $user->getId()]); return $this->findBy(['templateOwner' => $user->getId()]);
} }
/**
* Finds all templates
*/
public function findTemplate()
{
$qb = $this->createQueryBuilder('u');
$qb->where('u.templateOwner is NOT NULL');
return $qb->getQuery()->getResult();
}
} }

View File

@@ -20,6 +20,8 @@ class PDF
protected $records; protected $records;
protected $pdf; protected $pdf;
const LAYOUT_FEEDBACK = 'feedback';
const LAYOUT_FEEDBACKONLY = 'feedbackOnly';
const LAYOUT_PREVIEW = 'preview'; const LAYOUT_PREVIEW = 'preview';
const LAYOUT_PREVIEWCAPTION = 'previewCaption'; const LAYOUT_PREVIEWCAPTION = 'previewCaption';
const LAYOUT_PREVIEWCAPTIONTDM = 'previewCaptionTdm'; const LAYOUT_PREVIEWCAPTIONTDM = 'previewCaptionTdm';
@@ -27,64 +29,10 @@ class PDF
const LAYOUT_THUMBNAILGRID = 'thumbnailGrid'; const LAYOUT_THUMBNAILGRID = 'thumbnailGrid';
const LAYOUT_CAPTION = 'caption'; const LAYOUT_CAPTION = 'caption';
public function __construct(Application $app, array $records, $layout) public function __construct(Application $app)
{ {
$this->app = $app; $this->app = $app;
$list = [];
foreach ($records as $record) {
switch ($layout) {
default:
throw new \Exception('Unknown layout');
break;
case self::LAYOUT_PREVIEW:
case self::LAYOUT_PREVIEWCAPTION:
case self::LAYOUT_PREVIEWCAPTIONTDM:
try {
$subdef = $record->get_subdef('preview');
// fallback to thumbnail ( video, sound, doc ) ..
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
$subdef = $record->get_thumbnail();
}
if (!$subdef->is_physically_present()) {
continue 2;
}
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
continue 2;
}
} catch (\Exception $e) {
continue 2;
}
break;
case self::LAYOUT_THUMBNAILLIST:
case self::LAYOUT_THUMBNAILGRID:
try {
$subdef = $record->get_thumbnail();
if (!$subdef->is_physically_present()) {
continue 2;
}
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
continue 2;
}
} catch (\Exception $e) {
continue 2;
}
break;
case self::LAYOUT_CAPTION:
break;
}
$record->setNumber(count($list) + 1);
$list[] = $record;
}
$this->records = $list;
$pdf = new PhraseaPDF("P", "mm", "A4", true, 'UTF-8', false); $pdf = new PhraseaPDF("P", "mm", "A4", true, 'UTF-8', false);
$pdf->SetAuthor("Phraseanet"); $pdf->SetAuthor("Phraseanet");
@@ -92,29 +40,6 @@ class PDF
$pdf->SetDisplayMode("fullpage", "single"); $pdf->SetDisplayMode("fullpage", "single");
$this->pdf = $pdf; $this->pdf = $pdf;
switch ($layout) {
case self::LAYOUT_PREVIEW:
$this->print_preview(false, false);
break;
case self::LAYOUT_PREVIEWCAPTION:
$this->print_preview(false, true);
break;
case self::LAYOUT_PREVIEWCAPTIONTDM:
$this->print_preview(true, true);
break;
case self::LAYOUT_THUMBNAILLIST:
$this->print_thumbnailList();
break;
case self::LAYOUT_THUMBNAILGRID:
$this->print_thumbnailGrid();
break;
case self::LAYOUT_CAPTION:
$this->print_caption();
break;
}
return $this;
} }
public function render() public function render()
@@ -123,447 +48,4 @@ class PDF
return $this->pdf->Output('', 'S'); return $this->pdf->Output('', 'S');
} }
protected function print_thumbnailGrid($links = false)
{
$NDiapoW = 3;
$NDiapoH = 4;
$this->pdf->AddPage();
$oldMargins = $this->pdf->getMargins();
$tmargin = $oldMargins['top'];
$lmargin = $oldMargins['left'];
$bmargin = $oldMargins['bottom'];
$rmargin = $oldMargins['right'];
$this->pdf->SetLeftMargin($lmargin + 55);
$clientW = $this->pdf->getPageWidth() - $lmargin - $rmargin;
$clientH = $this->pdf->getPageHeight() - $tmargin - $bmargin;
$DiapoW = floor($clientW / $NDiapoW);
$DiapoH = floor($clientH / $NDiapoH);
$TitleH = 5;
$ImgSize = min($DiapoW, ($DiapoH - $TitleH)) - 5;
$npages = ceil(count($this->records) / ($NDiapoW * $NDiapoH));
$irow = $ipage = 0;
$icol = -1;
foreach ($this->records as $rec) {
/* @var \record_adapter $rec */
if (++$icol >= $NDiapoW) {
$icol = 0;
if (++$irow >= $NDiapoH) {
$irow = 0;
$ipage++;
$this->pdf->AddPage();
}
}
$fimg = null;
$himg = 0;
$subdef = $rec->get_subdef('preview');
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
$subdef = $rec->get_thumbnail();
}
$fimg = $subdef->getRealPath();
if (!$this->app->getAclForUser($this->app->getAuthenticatedUser())->has_right_on_base($rec->getBaseId(), \ACL::NOWATERMARK)
&& $subdef->get_type() == \media_subdef::TYPE_IMAGE) {
$fimg = \recordutils_image::watermark($this->app, $subdef);
}
$wimg = $himg = $ImgSize;
if ($subdef->get_height() > 0 && $subdef->get_width() > 0) {
if ($subdef->get_width() > $subdef->get_height())
$himg = $wimg * $subdef->get_height() / $subdef->get_width();
else
$wimg = $himg * $subdef->get_width() / $subdef->get_height();
}
if ($fimg) {
$x = $lmargin + ($icol * $DiapoW);
$y = $tmargin + ($irow * $DiapoH);
$this->pdf->SetDrawColor(0);
$this->pdf->Rect($x, $y, $DiapoW, $DiapoH, "D");
$this->pdf->SetXY($x, $y + 1);
$this->pdf->SetFont(PhraseaPDF::FONT, '', 10);
$t = $irow . '-' . $x;
$t = $rec->get_title();
if ($links) {
$lk = $this->pdf->AddLink();
$this->pdf->SetLink($lk, 0, $npages + $rec->getNumber());
$this->pdf->Image(
$fimg
, $x + (($DiapoW - $wimg) / 2)
, $TitleH + $y + (($DiapoH - $TitleH - $himg) / 2)
, $wimg, $himg
, null, $lk
);
} else {
$this->pdf->Image($fimg
, $x + (($DiapoW - $wimg) / 2)
, $TitleH + $y + (($DiapoH - $TitleH - $himg) / 2)
, $wimg, $himg
);
}
$this->pdf->MultiCell($DiapoW, $TitleH, $t, '0', 'C', false);
}
}
$this->pdf->SetLeftMargin($oldMargins['left']);
}
protected function print_thumbnailList()
{
$this->pdf->AddPage();
$oldMargins = $this->pdf->getMargins();
$lmargin = $oldMargins['left'];
$rmargin = $oldMargins['right'];
$this->pdf->SetLeftMargin($lmargin + 55);
$ndoc = 0;
foreach ($this->records as $rec) {
/* @var \record_adapter $rec */
$subdef = $rec->get_subdef('thumbnail');
$fimg = $subdef->getRealPath();
$wimg = $himg = 50;
// 1px = 3.77952 mm
$finalWidth = round($subdef->get_width() / 3.779528, 2);
$finalHeight = round($subdef->get_height() / 3.779528, 2);
$aspectH = $finalWidth/$finalHeight;
$aspectW = $finalHeight/$finalWidth;
if ($finalWidth > 0 && $finalHeight > 0) {
if ($finalWidth > $finalHeight && $finalWidth > $wimg) {
$finalWidth = $wimg;
$finalHeight = $wimg * $aspectW;
} else if ($finalHeight > $finalWidth && $finalHeight > $himg) {
$finalHeight = $himg;
$finalWidth = $himg * $aspectH;
} else if ($finalHeight == $finalWidth & $finalWidth > $wimg) {
$finalHeight = $wimg;
$finalWidth = $himg;
}
}
if ($this->pdf->GetY() > $this->pdf->getPageHeight() - (6 + $finalHeight + 20))
$this->pdf->AddPage();
$title = "record : " . $rec->get_title();
$y = $this->pdf->GetY();
$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->SetLeftMargin($lmargin + 55);
$this->pdf->SetY($y + 2);
if ($fimg) {
$y = $this->pdf->GetY();
$this->pdf->Image($fimg, $lmargin, $y, $finalWidth, $finalHeight);
$this->pdf->SetY($y + 3);
}
$nf = 0;
$this->pdf->SetX($lmargin + 55);
$p0 = $this->pdf->PageNo();
$y0 = $this->pdf->GetY();
foreach ($rec->get_caption()->get_fields() as $field) {
/* @var $field caption_field */
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $field->get_name() . " : ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $field->get_serialized_values());
$this->pdf->Write(6, "\n");
$nf++;
}
if ($this->pdf->PageNo() == $p0 && ($this->pdf->GetY() - $y0) < $finalHeight)
$this->pdf->SetY($y0 + $finalHeight);
$ndoc++;
}
$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)
{
if ($withtdm === true) {
$this->print_thumbnailGrid($this->pdf, $this->records, true);
}
foreach ($this->records as $krec => $rec) {
/* @var \record_adapter $rec */
$this->pdf->AddPage();
if ($withtdm === "CALCPAGES") {
$rec->setNumber($this->pdf->PageNo());
}
$lmargin = $this->pdf->GetX();
$himg = 0;
$y = 0;
$miniConv = NULL;
$LEFT__TEXT = "";
$LEFT__IMG = NULL;
$RIGHT_TEXT = "";
$RIGHT_IMG = NULL;
$LEFT__IMG = $this->app['root.path'] . "/config/minilogos/logopdf_" . $rec->getDataboxId() . ".jpg";
if (!is_file($LEFT__IMG)) {
$databox = $rec->getDatabox();
$str = $databox->get_sxml_structure();
$vn = (string) ($str->pdfPrintLogo);
if (($vn * 1) == 1) {
$LEFT__TEXT = $databox->get_label($this->app['locale']);
}
}
$collection = \collection::getByBaseId($this->app, $rec->getBaseId());
$vn = "";
if (false !== $str = simplexml_load_string($collection->get_prefs())) {
$vn = (string) ($str->pdfPrintappear);
}
if ($vn == "" || $vn == "1") {
$RIGHT_TEXT = \phrasea::bas_labels($rec->getBaseId(), $this->app);
} elseif ($vn == "2") {
$RIGHT_IMG = $this->app['root.path'] . "/config/minilogos/" . $rec->getBaseId();
}
$xtmp = $this->pdf->GetX();
$ytmp = $this->pdf->GetY();
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->SetFillColor(220, 220, 220);
$y = $this->pdf->GetY();
$this->pdf->MultiCell(95, 7, $LEFT__TEXT, "LTB", "L", 1);
$y2 = $this->pdf->GetY();
$h = $y2 - $y;
$this->pdf->SetY($y);
$this->pdf->SetX(105);
$this->pdf->Cell(95, $h, $RIGHT_TEXT, "TBR", 1, "R", 1);
if ($LEFT__TEXT == "" && is_file($LEFT__IMG)) {
if ($size = @getimagesize($LEFT__IMG)) {
$wmm = (int) $size[0] * 25.4 / 72;
$hmm = (int) $size[1] * 25.4 / 72;
if ($hmm > 6) {
$coeff = $hmm / 6;
$wmm = (int) $wmm / $coeff;
$hmm = (int) $hmm / $coeff;
}
$this->pdf->Image($LEFT__IMG, $xtmp + 0.5, $ytmp + 0.5, $wmm, $hmm);
}
}
if ($RIGHT_IMG != NULL && is_file($RIGHT_IMG)) {
if ($size = @getimagesize($RIGHT_IMG)) {
if ($size[2] == '1') {
if (!isset($miniConv[$RIGHT_IMG])) {
$tmp_filename = tempnam('minilogos/', 'gif4fpdf');
$img = imagecreatefromgif($RIGHT_IMG);
imageinterlace($img, 0);
imagepng($img, $tmp_filename);
rename($tmp_filename, $tmp_filename . '.png');
$miniConv[$RIGHT_IMG] = $tmp_filename . '.png';
$RIGHT_IMG = $tmp_filename . '.png';
} else
$RIGHT_IMG = $miniConv[$RIGHT_IMG];
$wmm = (int) $size[0] * 25.4 / 72;
$hmm = (int) $size[1] * 25.4 / 72;
if ($hmm > 6) {
$coeff = $hmm / 6;
$wmm = (int) $wmm / $coeff;
$hmm = (int) $hmm / $coeff;
}
$tt = 0;
if ($hmm < 6)
$tt = (6 - $hmm) / 2;
$this->pdf->Image($RIGHT_IMG, 200 - 0.5 - $wmm, $ytmp + 0.5 + $tt);
} else {
$wmm = (int) $size[0] * 25.4 / 72;
$hmm = (int) $size[1] * 25.4 / 72;
if ($hmm > 6) {
$coeff = $hmm / 6;
$wmm = (int) $wmm / $coeff;
$hmm = (int) $hmm / $coeff;
}
$this->pdf->Image($RIGHT_IMG, 200 - 0.5 - $wmm, $ytmp + 0.5);
}
}
}
$y = $this->pdf->GetY() + 5;
$subdef = $rec->get_subdef('preview');
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
$subdef = $rec->get_thumbnail();
}
$f = $subdef->getRealPath();
if (!$this->app->getAclForUser($this->app->getAuthenticatedUser())->has_right_on_base($rec->getBaseId(), \ACL::NOWATERMARK)
&& $subdef->get_type() == \media_subdef::TYPE_IMAGE)
$f = \recordutils_image::watermark($this->app, $subdef);
// original height / original width x new width = new height
$wimg = $himg = 150; // preview dans un carre de 150 mm
// 1px = 3.77952 mm
$finalWidth = round($subdef->get_width() / 3.779528, 2);
$finalHeight = round($subdef->get_height() / 3.779528, 2);
$aspectH = $finalWidth/$finalHeight;
$aspectW = $finalHeight/$finalWidth;
if ($finalWidth > 0 && $finalHeight > 0) {
if ($finalWidth > $finalHeight && $finalWidth > $wimg) {
$finalWidth = $wimg;
$finalHeight = $wimg * $aspectW;
} else if ($finalHeight > $finalWidth && $finalHeight > $himg) {
$finalHeight = $himg;
$finalWidth = $himg * $aspectH;
} else if ($finalHeight == $finalWidth & $finalWidth > $wimg) {
$finalHeight = $wimg;
$finalWidth = $himg;
}
}
$this->pdf->Image($f, (210 - $finalWidth) / 2, $y, $finalWidth, $finalHeight);
if ($miniConv != NULL) {
foreach ($miniConv as $oneF)
unlink($oneF);
}
$this->pdf->SetXY($lmargin, $y += ( $finalHeight + 5));
$nf = 0;
if ($write_caption) {
foreach ($rec->get_caption()->get_fields() as $field) {
/* @var $field caption_field */
if ($nf > 0) {
$this->pdf->Write(6, "\n");
}
$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);
$nf++;
}
}
}
return;
}
} }

View File

@@ -0,0 +1,789 @@
<?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\Out\Module;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Out\Tool\PhraseaPDF;
use Alchemy\Phrasea\Helper\Record\Printer;
use Alchemy\Phrasea\Model\Entities\ValidationParticipant;
use \IntlDateFormatter as DateFormatter;
class PDFRecords extends PDF
{
/** @var Printer */
private $printer;
public function __construct(Application $app, Printer $printer, $layout)
{
parent::__construct($app);
$this->printer = $printer;
$records = $printer->get_elements();
$list = [];
foreach ($records as $record) {
switch ($layout) {
default:
throw new \Exception('Unknown layout');
break;
case self::LAYOUT_FEEDBACK:
case self::LAYOUT_FEEDBACKONLY:
case self::LAYOUT_PREVIEW:
case self::LAYOUT_PREVIEWCAPTION:
case self::LAYOUT_PREVIEWCAPTIONTDM:
try {
$subdef = $record->get_subdef('preview');
// fallback to thumbnail ( video, sound, doc ) ..
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
$subdef = $record->get_thumbnail();
}
if (!$subdef->is_physically_present()) {
continue 2;
}
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
continue 2;
}
} catch (\Exception $e) {
continue 2;
}
break;
case self::LAYOUT_THUMBNAILLIST:
case self::LAYOUT_THUMBNAILGRID:
try {
$subdef = $record->get_thumbnail();
if (!$subdef->is_physically_present()) {
continue 2;
}
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
continue 2;
}
} catch (\Exception $e) {
continue 2;
}
break;
case self::LAYOUT_CAPTION:
break;
}
$record->setNumber(count($list) + 1);
$list[] = $record;
}
$this->records = $list;
switch ($layout) {
case self::LAYOUT_FEEDBACK:
$this->print_preview(false, true, true);
break;
case self::LAYOUT_FEEDBACKONLY:
$this->print_preview(false, false, true);
break;
case self::LAYOUT_PREVIEW:
$this->print_preview(false, false, false);
break;
case self::LAYOUT_PREVIEWCAPTION:
$this->print_preview(false, true, false);
break;
case self::LAYOUT_PREVIEWCAPTIONTDM:
$this->print_preview(true, true, false);
break;
case self::LAYOUT_THUMBNAILLIST:
$this->print_thumbnailList();
break;
case self::LAYOUT_THUMBNAILGRID:
$this->print_thumbnailGrid();
break;
case self::LAYOUT_CAPTION:
$this->print_caption();
break;
}
}
protected function print_thumbnailGrid($links = false)
{
$NDiapoW = 3;
$NDiapoH = 4;
$this->pdf->AddPage();
$oldMargins = $this->pdf->getMargins();
$tmargin = $oldMargins['top'];
$lmargin = $oldMargins['left'];
$bmargin = $oldMargins['bottom'];
$rmargin = $oldMargins['right'];
$this->pdf->SetLeftMargin($lmargin + 55);
$clientW = $this->pdf->getPageWidth() - $lmargin - $rmargin;
$clientH = $this->pdf->getPageHeight() - $tmargin - $bmargin;
$DiapoW = floor($clientW / $NDiapoW);
$DiapoH = floor($clientH / $NDiapoH);
$TitleH = 5;
$ImgSize = min($DiapoW, ($DiapoH - $TitleH)) - 5;
$npages = ceil(count($this->records) / ($NDiapoW * $NDiapoH));
$irow = $ipage = 0;
$icol = -1;
foreach ($this->records as $rec) {
/* @var \record_adapter $rec */
if (++$icol >= $NDiapoW) {
$icol = 0;
if (++$irow >= $NDiapoH) {
$irow = 0;
$ipage++;
$this->pdf->AddPage();
}
}
$fimg = null;
$himg = 0;
$subdef = $rec->get_subdef('preview');
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
$subdef = $rec->get_thumbnail();
}
$fimg = $subdef->getRealPath();
if (!$this->app->getAclForUser($this->app->getAuthenticatedUser())->has_right_on_base($rec->getBaseId(), \ACL::NOWATERMARK)
&& $subdef->get_type() == \media_subdef::TYPE_IMAGE) {
$fimg = \recordutils_image::watermark($this->app, $subdef);
}
$wimg = $himg = $ImgSize;
if ($subdef->get_height() > 0 && $subdef->get_width() > 0) {
if ($subdef->get_width() > $subdef->get_height())
$himg = $wimg * $subdef->get_height() / $subdef->get_width();
else
$wimg = $himg * $subdef->get_width() / $subdef->get_height();
}
if ($fimg) {
$x = $lmargin + ($icol * $DiapoW);
$y = $tmargin + ($irow * $DiapoH);
$this->pdf->SetDrawColor(0);
$this->pdf->Rect($x, $y, $DiapoW, $DiapoH, "D");
$this->pdf->SetXY($x, $y + 1);
$this->pdf->SetFont(PhraseaPDF::FONT, '', 10);
$t = $irow . '-' . $x;
$t = $rec->get_title();
if ($links) {
$lk = $this->pdf->AddLink();
$this->pdf->SetLink($lk, 0, $npages + $rec->getNumber());
$this->pdf->Image(
$fimg
, $x + (($DiapoW - $wimg) / 2)
, $TitleH + $y + (($DiapoH - $TitleH - $himg) / 2)
, $wimg, $himg
, null, $lk
);
} else {
$this->pdf->Image($fimg
, $x + (($DiapoW - $wimg) / 2)
, $TitleH + $y + (($DiapoH - $TitleH - $himg) / 2)
, $wimg, $himg
);
}
$this->pdf->MultiCell($DiapoW, $TitleH, $t, '0', 'C', false);
}
}
$this->pdf->SetLeftMargin($oldMargins['left']);
}
protected function print_thumbnailList()
{
$this->pdf->AddPage();
$oldMargins = $this->pdf->getMargins();
$lmargin = $oldMargins['left'];
$rmargin = $oldMargins['right'];
$this->pdf->SetLeftMargin($lmargin + 55);
$ndoc = 0;
foreach ($this->records as $rec) {
/* @var \record_adapter $rec */
$subdef = $rec->get_subdef('thumbnail');
$fimg = $subdef->getRealPath();
$wimg = $himg = 50;
// 1px = 3.77952 mm
$finalWidth = round($subdef->get_width() / 3.779528, 2);
$finalHeight = round($subdef->get_height() / 3.779528, 2);
$aspectH = $finalWidth/$finalHeight;
$aspectW = $finalHeight/$finalWidth;
if ($finalWidth > 0 && $finalHeight > 0) {
if ($finalWidth > $finalHeight && $finalWidth > $wimg) {
$finalWidth = $wimg;
$finalHeight = $wimg * $aspectW;
} else if ($finalHeight > $finalWidth && $finalHeight > $himg) {
$finalHeight = $himg;
$finalWidth = $himg * $aspectH;
} else if ($finalHeight == $finalWidth & $finalWidth > $wimg) {
$finalHeight = $wimg;
$finalWidth = $himg;
}
}
if ($this->pdf->GetY() > $this->pdf->getPageHeight() - (6 + $finalHeight + 20))
$this->pdf->AddPage();
$title = "record : " . $rec->get_title();
$y = $this->pdf->GetY();
$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->SetLeftMargin($lmargin + 55);
$this->pdf->SetY($y + 2);
if ($fimg) {
$y = $this->pdf->GetY();
$this->pdf->Image($fimg, $lmargin, $y, $finalWidth, $finalHeight);
$this->pdf->SetY($y + 3);
}
$nf = 0;
$this->pdf->SetX($lmargin + 55);
$p0 = $this->pdf->PageNo();
$y0 = $this->pdf->GetY();
foreach ($rec->get_caption()->get_fields() as $field) {
/* @var $field caption_field */
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $field->get_name() . " : ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $field->get_serialized_values());
$this->pdf->Write(6, "\n");
$nf++;
}
if ($this->pdf->PageNo() == $p0 && ($this->pdf->GetY() - $y0) < $finalHeight)
$this->pdf->SetY($y0 + $finalHeight);
$ndoc++;
}
$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) {
if ($field->get_databox_field()->get_gui_visible()) {
$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, $withfeedback)
{
$basket = $validation = null;
if($this->printer->is_basket()) {
$basket = $this->printer->get_original_basket();
if($withfeedback) {
// first page : validation informations
$validation = $basket->getValidation();
$this->pdf->AddPage();
$this->pdf->SetY(20);
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 14);
$this->pdf->Cell(0, 0,
$this->app->trans("print_feedback:: Feedback on basket %name%", ['%name%'=>$basket->getName()]),
'', 1, 'C', false);
$this->pdf->SetY($this->pdf->GetY()+10);
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: Document generated on : ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $this->formatDate(new \DateTime('now')));
$this->pdf->Write(12, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: Feedback initiated by : ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $this->getDisplayName($validation->getInitiator()));
$this->pdf->Write(6, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: Feedback initiated on : ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $this->formatDate($validation->getCreated()));
$this->pdf->Write(6, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: Feedback expiring on : ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $this->formatDate($validation->getExpires()));
$this->pdf->Write(12, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$validation->isFinished() ? $this->pdf->Write(5, $this->app->trans("print_feedback:: Feedback expired")) : $this->pdf->Write(5, $this->app->trans("print_feedback:: Feedback active"));
$this->pdf->Write(12, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: Participants : "));
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
foreach ($validation->getParticipants() as $participant) {
$this->pdf->Write(5, "\n - " . $this->getDisplayName($participant->getUser()));
}
}
}
if ($withtdm === true) {
$this->print_thumbnailGrid($this->pdf, $this->records, true);
}
foreach ($this->records as $krec => $rec) {
/* @var \record_adapter $rec */
$this->pdf->AddPage();
if ($withtdm === "CALCPAGES") {
$rec->setNumber($this->pdf->PageNo());
}
$lmargin = $this->pdf->GetX();
$himg = 0;
$y = 0;
$miniConv = NULL;
$LEFT__TEXT = "";
$LEFT__IMG = NULL;
$RIGHT_TEXT = "";
$RIGHT_IMG = NULL;
$LEFT__IMG = $this->app['root.path'] . "/config/minilogos/logopdf_" . $rec->getDataboxId() . ".jpg";
if (!is_file($LEFT__IMG)) {
$databox = $rec->getDatabox();
$str = $databox->get_sxml_structure();
$vn = (string) ($str->pdfPrintLogo);
if (($vn * 1) == 1) {
$LEFT__TEXT = $databox->get_label($this->app['locale']);
}
}
$collection = \collection::getByBaseId($this->app, $rec->getBaseId());
$vn = "";
if (false !== $str = simplexml_load_string($collection->get_prefs())) {
$vn = (string) ($str->pdfPrintappear);
}
if ($vn == "" || $vn == "1") {
$RIGHT_TEXT = \phrasea::bas_labels($rec->getBaseId(), $this->app);
} elseif ($vn == "2") {
$RIGHT_IMG = $this->app['root.path'] . "/config/minilogos/" . $rec->getBaseId();
}
$xtmp = $this->pdf->GetX();
$ytmp = $this->pdf->GetY();
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->SetFillColor(220, 220, 220);
$y = $this->pdf->GetY();
$this->pdf->MultiCell(95, 7, $LEFT__TEXT, "LTB", "L", 1);
$y2 = $this->pdf->GetY();
$h = $y2 - $y;
$this->pdf->SetY($y);
$this->pdf->SetX(105);
$this->pdf->Cell(95, $h, $RIGHT_TEXT, "TBR", 1, "R", 1);
if($basket) {
$ord = $basket->getElementByRecord($this->app, $rec)->getOrd();
$this->pdf->SetY($y);
$this->pdf->SetX(10);
$this->pdf->Cell(190, $h, $ord, "", 1, "C", 0);
}
if ($LEFT__TEXT == "" && is_file($LEFT__IMG)) {
if ($size = @getimagesize($LEFT__IMG)) {
$wmm = (int) $size[0] * 25.4 / 72;
$hmm = (int) $size[1] * 25.4 / 72;
if ($hmm > 6) {
$coeff = $hmm / 6;
$wmm = (int) $wmm / $coeff;
$hmm = (int) $hmm / $coeff;
}
$this->pdf->Image($LEFT__IMG, $xtmp + 0.5, $ytmp + 0.5, $wmm, $hmm);
}
}
if ($RIGHT_IMG != NULL && is_file($RIGHT_IMG)) {
if ($size = @getimagesize($RIGHT_IMG)) {
if ($size[2] == '1') {
if (!isset($miniConv[$RIGHT_IMG])) {
$tmp_filename = tempnam('minilogos/', 'gif4fpdf');
$img = imagecreatefromgif($RIGHT_IMG);
imageinterlace($img, 0);
imagepng($img, $tmp_filename);
rename($tmp_filename, $tmp_filename . '.png');
$miniConv[$RIGHT_IMG] = $tmp_filename . '.png';
$RIGHT_IMG = $tmp_filename . '.png';
} else
$RIGHT_IMG = $miniConv[$RIGHT_IMG];
$wmm = (int) $size[0] * 25.4 / 72;
$hmm = (int) $size[1] * 25.4 / 72;
if ($hmm > 6) {
$coeff = $hmm / 6;
$wmm = (int) $wmm / $coeff;
$hmm = (int) $hmm / $coeff;
}
$tt = 0;
if ($hmm < 6)
$tt = (6 - $hmm) / 2;
$this->pdf->Image($RIGHT_IMG, 200 - 0.5 - $wmm, $ytmp + 0.5 + $tt);
} else {
$wmm = (int) $size[0] * 25.4 / 72;
$hmm = (int) $size[1] * 25.4 / 72;
if ($hmm > 6) {
$coeff = $hmm / 6;
$wmm = (int) $wmm / $coeff;
$hmm = (int) $hmm / $coeff;
}
$this->pdf->Image($RIGHT_IMG, 200 - 0.5 - $wmm, $ytmp + 0.5);
}
}
}
$y = $this->pdf->GetY() + 5;
$subdef = $rec->get_subdef('preview');
if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) {
$subdef = $rec->get_thumbnail();
}
$f = $subdef->getRealPath();
if (!$this->app->getAclForUser($this->app->getAuthenticatedUser())->has_right_on_base($rec->getBaseId(), \ACL::NOWATERMARK)
&& $subdef->get_type() == \media_subdef::TYPE_IMAGE)
$f = \recordutils_image::watermark($this->app, $subdef);
// original height / original width x new width = new height
$wimg = $himg = 150; // preview dans un carre de 150 mm
// 1px = 3.77952 mm
$finalWidth = round($subdef->get_width() / 3.779528, 2);
$finalHeight = round($subdef->get_height() / 3.779528, 2);
$aspectH = $finalWidth/$finalHeight;
$aspectW = $finalHeight/$finalWidth;
if ($finalWidth > 0 && $finalHeight > 0) {
if ($finalWidth > $finalHeight && $finalWidth > $wimg) {
$finalWidth = $wimg;
$finalHeight = $wimg * $aspectW;
} else if ($finalHeight > $finalWidth && $finalHeight > $himg) {
$finalHeight = $himg;
$finalWidth = $himg * $aspectH;
} else if ($finalHeight == $finalWidth & $finalWidth > $wimg) {
$finalHeight = $wimg;
$finalWidth = $himg;
}
}
$this->pdf->Image($f, (210 - $finalWidth) / 2, $y, $finalWidth, $finalHeight);
if ($miniConv != NULL) {
foreach ($miniConv as $oneF)
unlink($oneF);
}
$this->pdf->SetXY($lmargin, $y += ( $finalHeight + 5));
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: record title: ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $rec->get_title());
$this->pdf->Write(6, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: record id: ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $rec->getRecordId());
$this->pdf->Write(6, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: base name: ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $rec->getDatabox()->get_label($this->app['locale']));
$this->pdf->Write(6, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: originale filename: ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $rec->get_original_name());
$this->pdf->Write(6, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: document Uuid: ") . " ");
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$this->pdf->Write(5, $rec->getUUID());
$this->pdf->Write(6, "\n");
$nf = 0;
if($basket && $validation) {
/** @var ValidationParticipant $participant */
if ($nf > 0) {
$this->pdf->Write(6, "\n");
}
$this->pdf->Write(12, "\n");
$this->pdf->SetFont(PhraseaPDF::FONT, 'B', 12);
$this->pdf->Write(5, $this->app->trans("print_feedback:: Votes :"));
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
$basketElement = $basket->getElementByRecord($this->app, $rec);
$iparticipant = 0;
foreach ($validation->getParticipants() as $participant) {
$this->pdf->Write(6, "\n");
if($iparticipant++ > 0) {
// $this->pdf->SetY($this->pdf->GetY()+1);
}
$validationData = $basketElement->getUserValidationDatas($participant->getUser());
$this->pdf->Write(5, '- ' . $this->getDisplayName($participant->getUser(), true). " : ");
$r = $validationData->getAgreement();
$this->pdf->SetX(100);
if ($r === null) {
$this->pdf->Write(0, $this->app->trans("print_feedback:: non voté"));
}
else {
if($r) {
$this->pdf->SetTextColor(0, 127, 0);
$this->pdf->Write(0, $this->app->trans("print_feedback:: Oui"));
}
else {
$this->pdf->SetTextColor(200, 0, 0);
$this->pdf->Write(0, $this->app->trans("print_feedback:: Non"));
}
$this->pdf->SetTextColor(0);
$this->pdf->Write(0, " (" . $this->formatDate($validationData->getUpdated()) . ")");
}
if (($note = (string)($validationData->getNote())) !== '') {
$this->pdf->SetFont(PhraseaPDF::FONT, 'I', 11);
$this->pdf->Write(5,"\n");
$this->pdf->SetX(100);
$this->pdf->MultiCell(95, 0, $note, '', "L", false);
$this->pdf->SetFont(PhraseaPDF::FONT, '', 12);
}
$nf++;
}
}
if ($write_caption) {
$this->pdf->Write(12, "\n");
foreach ($rec->get_caption()->get_fields() as $field) {
/* @var $field caption_field */
if ($field->get_databox_field()->get_gui_visible()) {
if ($nf > 0) {
$this->pdf->Write(6, "\n");
}
$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);
$nf++;
}
}
}
}
return;
}
private function formatDate(\DateTime $date)
{
$locale = $this->app['locale'];
switch ($locale) {
case 'fr':
$fmt = new DateFormatter(
'fr_FR',
DateFormatter::LONG,
DateFormatter::NONE
);
$date_formated = $fmt->format($date);
break;
case 'en':
$fmt = new DateFormatter(
'en_EN',
DateFormatter::LONG,
DateFormatter::NONE
);
$date_formated = $fmt->format($date);
break;
case 'de':
$fmt = new DateFormatter(
'de_DE',
DateFormatter::LONG,
DateFormatter::NONE
);
$date_formated = $fmt->format($date);
break;
default:
$fmt = new DateFormatter(
'en_EN',
DateFormatter::LONG,
DateFormatter::NONE ,
null,
null,
'yyyy/mm/dd'
);
$date_formated = $fmt->format($date);
break;
}
return $date_formated;
}
private function getDisplayName($user, $short = false)
{
$displayName = '';
if (trim($user->getLastName()) !== '' || trim($user->getFirstName()) !== '') {
$displayName = $user->getFirstName() . ('' !== $user->getFirstName() && '' !== $user->getLastName() ? ' ' : '') . $user->getLastName() ;
if ($short) {
return $displayName;
}
}
$email = trim($user->getEmail());
if ($email === '') {
$email = $user->getLogin();
}
if ($displayName !== '') {
return $displayName . ", " . $email;
} else {
return $email;
}
}
}

View File

@@ -668,17 +668,20 @@ class ElasticSearchEngine implements SearchEngineInterface
} }
// fields aggregates // fields aggregates
$structure = $this->context_factory->getLimitedStructure($options); $structure = $this->context_factory->getLimitedStructure($options);
foreach ($structure->getFacetFields() as $name => $field) { foreach($structure->getAllFields() as $name => $field) {
// 2015-05-26 (mdarse) Removed databox filtering. $size = $this->options->getAggregableFieldLimit($name);
// It was already done by the ACL filter in the query scope, so no if ($size !== databox_field::FACET_DISABLED) {
// document that shouldn't be displayed can go this far. if ($size === databox_field::FACET_NO_LIMIT) {
$agg = [ $size = ESField::FACET_NO_LIMIT;
'terms' => [ }
'field' => $field->getIndexField(true), $agg = [
'size' => $field->getFacetValuesLimit() 'terms' => [
] 'field' => $field->getIndexField(true),
]; 'size' => $size
$aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg); ]
];
$aggs[$name] = AggregationHelper::wrapPrivateFieldAggregation($field, $agg);
}
} }
return $aggs; return $aggs;

View File

@@ -9,6 +9,10 @@
*/ */
namespace Alchemy\Phrasea\SearchEngine\Elastic; namespace Alchemy\Phrasea\SearchEngine\Elastic;
use databox_field;
use igorw;
class ElasticsearchOptions class ElasticsearchOptions
{ {
const POPULATE_ORDER_RID = "RECORD_ID"; const POPULATE_ORDER_RID = "RECORD_ID";
@@ -35,7 +39,7 @@ class ElasticsearchOptions
private $populateDirection; private $populateDirection;
/** @var int[] */ /** @var int[] */
private $_customValues; private $_customValues = [];
private $activeTab; private $activeTab;
/** /**
@@ -57,14 +61,10 @@ class ElasticsearchOptions
'populate_order' => self::POPULATE_ORDER_RID, 'populate_order' => self::POPULATE_ORDER_RID,
'populate_direction' => self::POPULATE_DIRECTION_DESC, 'populate_direction' => self::POPULATE_DIRECTION_DESC,
'activeTab' => null, 'activeTab' => null,
'facets' => []
]; ];
foreach(self::getAggregableTechnicalFields() as $k => $f) {
$defaultOptions[$k.'_limit'] = 0;
}
$options = array_replace($defaultOptions, $options); $options = array_replace($defaultOptions, $options);
$self = new self(); $self = new self();
$self->setHost($options['host']); $self->setHost($options['host']);
$self->setPort($options['port']); $self->setPort($options['port']);
@@ -76,11 +76,10 @@ class ElasticsearchOptions
$self->setPopulateOrder($options['populate_order']); $self->setPopulateOrder($options['populate_order']);
$self->setPopulateDirection($options['populate_direction']); $self->setPopulateDirection($options['populate_direction']);
$self->setActiveTab($options['activeTab']); $self->setActiveTab($options['activeTab']);
foreach(self::getAggregableTechnicalFields() as $k => $f) { foreach($options['facets'] as $fieldname=>$attributes) {
$self->setAggregableFieldLimit($k, $options[$k.'_limit']); $self->setAggregableField($fieldname, $attributes);
} }
return $self; return $self;
} }
@@ -99,10 +98,11 @@ class ElasticsearchOptions
'highlight' => $this->highlight, 'highlight' => $this->highlight,
'populate_order' => $this->populateOrder, 'populate_order' => $this->populateOrder,
'populate_direction' => $this->populateDirection, 'populate_direction' => $this->populateDirection,
'activeTab' => $this->activeTab 'activeTab' => $this->activeTab,
'facets' => []
]; ];
foreach(self::getAggregableTechnicalFields() as $k => $f) { foreach($this->getAggregableFields() as $fieldname=>$attributes) {
$ret[$k.'_limit'] = $this->getAggregableFieldLimit($k); $ret['facets'][$fieldname] = $attributes;
} }
return $ret; return $ret;
@@ -222,12 +222,51 @@ class ElasticsearchOptions
public function setAggregableFieldLimit($key, $value) public function setAggregableFieldLimit($key, $value)
{ {
$this->_customValues[$key.'_limit'] = $value; if(is_null($this->getAggregableField($key))) {
$this->_customValues['facets'][$key] = [];
}
$this->_customValues['facets'][$key]['limit'] = $value;
}
public function setAggregableField($key, $attributes)
{
$this->getAggregableFields(); // ensure facets exists
$this->_customValues['facets'][$key] = $attributes;
} }
public function getAggregableFieldLimit($key) public function getAggregableFieldLimit($key)
{ {
return $this->_customValues[$key.'_limit']; $facet = $this->getAggregableField($key);
return (is_array($facet) && array_key_exists('limit', $facet)) ? $facet['limit'] : databox_field::FACET_DISABLED;
}
public function getAggregableField($key)
{
$facets = $this->getAggregableFields();
return array_key_exists($key, $facets) ? $facets[$key] : null;
}
/**
* @return array
*/
public function getAggregableFields()
{
if(!array_key_exists('facets', $this->_customValues) || !is_array($this->_customValues['facets'])) {
$this->_customValues['facets'] = [];
}
return $this->_customValues['facets'];
}
// set to change the facets order during admin/form save
public function reorderAggregableFields($facetNames)
{
$newFacets = [];
foreach ($facetNames as $name) {
if(($facet = $this->getAggregableField($name)) !== null) {
$newFacets[$name] = $facet;
}
}
$this->_customValues['facets'] = $newFacets;
} }
public function getActiveTab() public function getActiveTab()
@@ -241,56 +280,56 @@ class ElasticsearchOptions
public function __get($key) public function __get($key)
{ {
if(!array_key_exists($key, $this->_customValues)) { $keys = explode(':', $key);
$this->_customValues[$key] = 0;
} return igorw\get_in($this->_customValues, $keys);
return $this->_customValues[$key];
} }
public function __set($key, $value) public function __set($key, $value)
{ {
$this->_customValues[$key] = $value; $keys = explode(':', $key);
$this->_customValues = igorw\assoc_in($this->_customValues, $keys, $value);
} }
public static function getAggregableTechnicalFields() public static function getAggregableTechnicalFields()
{ {
return [ return [
'base_aggregate' => [ '_base' => [
'type' => 'string', 'type' => 'string',
'label' => 'prod::facet:base_label', 'label' => 'prod::facet:base_label',
'field' => "database", 'field' => "database",
'esfield' => 'databox_name', 'esfield' => 'databox_name',
'query' => 'database:%s', 'query' => 'database:%s',
], ],
'collection_aggregate' => [ '_collection' => [
'type' => 'string', 'type' => 'string',
'label' => 'prod::facet:collection_label', 'label' => 'prod::facet:collection_label',
'field' => "collection", 'field' => "collection",
'esfield' => 'collection_name', 'esfield' => 'collection_name',
'query' => 'collection:%s', 'query' => 'collection:%s',
], ],
'doctype_aggregate' => [ '_doctype' => [
'type' => 'string', 'type' => 'string',
'label' => 'prod::facet:doctype_label', 'label' => 'prod::facet:doctype_label',
'field' => "type", 'field' => "type",
'esfield' => 'type', 'esfield' => 'type',
'query' => 'type:%s', 'query' => 'type:%s',
], ],
'camera_model_aggregate' => [ '_camera_model' => [
'type' => 'string', 'type' => 'string',
'label' => 'Camera Model', 'label' => 'Camera Model',
'field' => "meta.CameraModel", 'field' => "meta.CameraModel",
'esfield' => 'metadata_tags.CameraModel', 'esfield' => 'metadata_tags.CameraModel',
'query' => 'meta.CameraModel:%s', 'query' => 'meta.CameraModel:%s',
], ],
'iso_aggregate' => [ '_iso' => [
'type' => 'number', 'type' => 'number',
'label' => 'ISO', 'label' => 'ISO',
'field' => "meta.ISO", 'field' => "meta.ISO",
'esfield' => 'metadata_tags.ISO', 'esfield' => 'metadata_tags.ISO',
'query' => 'meta.ISO=%s', 'query' => 'meta.ISO=%s',
], ],
'aperture_aggregate' => [ '_aperture' => [
'type' => 'number', 'type' => 'number',
'label' => 'Aperture', 'label' => 'Aperture',
'field' => "meta.Aperture", 'field' => "meta.Aperture",
@@ -300,7 +339,7 @@ class ElasticsearchOptions
return round($value, 1); return round($value, 1);
}, },
], ],
'shutterspeed_aggregate' => [ '_shutterspeed' => [
'type' => 'number', 'type' => 'number',
'label' => 'Shutter speed', 'label' => 'Shutter speed',
'field' => "meta.ShutterSpeed", 'field' => "meta.ShutterSpeed",
@@ -313,7 +352,7 @@ class ElasticsearchOptions
return $value . ' s.'; return $value . ' s.';
}, },
], ],
'flashfired_aggregate' => [ '_flashfired' => [
'type' => 'boolean', 'type' => 'boolean',
'label' => 'FlashFired', 'label' => 'FlashFired',
'field' => "meta.FlashFired", 'field' => "meta.FlashFired",
@@ -327,49 +366,49 @@ class ElasticsearchOptions
return array_key_exists($value, $map) ? $map[$value] : $value; return array_key_exists($value, $map) ? $map[$value] : $value;
}, },
], ],
'framerate_aggregate' => [ '_framerate' => [
'type' => 'number', 'type' => 'number',
'label' => 'FrameRate', 'label' => 'FrameRate',
'field' => "meta.FrameRate", 'field' => "meta.FrameRate",
'esfield' => 'metadata_tags.FrameRate', 'esfield' => 'metadata_tags.FrameRate',
'query' => 'meta.FrameRate=%s', 'query' => 'meta.FrameRate=%s',
], ],
'audiosamplerate_aggregate' => [ '_audiosamplerate' => [
'type' => 'number', 'type' => 'number',
'label' => 'Audio Samplerate', 'label' => 'Audio Samplerate',
'field' => "meta.AudioSamplerate", 'field' => "meta.AudioSamplerate",
'esfield' => 'metadata_tags.AudioSamplerate', 'esfield' => 'metadata_tags.AudioSamplerate',
'query' => 'meta.AudioSamplerate=%s', 'query' => 'meta.AudioSamplerate=%s',
], ],
'videocodec_aggregate' => [ '_videocodec' => [
'type' => 'string', 'type' => 'string',
'label' => 'Video codec', 'label' => 'Video codec',
'field' => "meta.VideoCodec", 'field' => "meta.VideoCodec",
'esfield' => 'metadata_tags.VideoCodec', 'esfield' => 'metadata_tags.VideoCodec',
'query' => 'meta.VideoCodec:%s', 'query' => 'meta.VideoCodec:%s',
], ],
'audiocodec_aggregate' => [ '_audiocodec' => [
'type' => 'string', 'type' => 'string',
'label' => 'Audio codec', 'label' => 'Audio codec',
'field' => "meta.AudioCodec", 'field' => "meta.AudioCodec",
'esfield' => 'metadata_tags.AudioCodec', 'esfield' => 'metadata_tags.AudioCodec',
'query' => 'meta.AudioCodec:%s', 'query' => 'meta.AudioCodec:%s',
], ],
'orientation_aggregate' => [ '_orientation' => [
'type' => 'string', 'type' => 'string',
'label' => 'Orientation', 'label' => 'Orientation',
'field' => "meta.Orientation", 'field' => "meta.Orientation",
'esfield' => 'metadata_tags.Orientation', 'esfield' => 'metadata_tags.Orientation',
'query' => 'meta.Orientation=%s', 'query' => 'meta.Orientation=%s',
], ],
'colorspace_aggregate' => [ '_colorspace' => [
'type' => 'string', 'type' => 'string',
'label' => 'Colorspace', 'label' => 'Colorspace',
'field' => "meta.ColorSpace", 'field' => "meta.ColorSpace",
'esfield' => 'metadata_tags.ColorSpace', 'esfield' => 'metadata_tags.ColorSpace',
'query' => 'meta.ColorSpace:%s', 'query' => 'meta.ColorSpace:%s',
], ],
'mimetype_aggregate' => [ '_mimetype' => [
'type' => 'string', 'type' => 'string',
'label' => 'MimeType', 'label' => 'MimeType',
'field' => "meta.MimeType", 'field' => "meta.MimeType",

View File

@@ -9,6 +9,7 @@
*/ */
namespace Alchemy\Phrasea\SearchEngine\Elastic; namespace Alchemy\Phrasea\SearchEngine\Elastic;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -17,6 +18,18 @@ use Symfony\Component\Validator\Constraints\Range;
class ElasticsearchSettingsFormType extends AbstractType class ElasticsearchSettingsFormType extends AbstractType
{ {
/** @var GlobalStructure */
private $globalStructure;
/** @var ElasticsearchOptions */
private $esSettings;
public function __construct(GlobalStructure $g, ElasticsearchOptions $settings)
{
$this->globalStructure = $g;
$this->esSettings = $settings;
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder
@@ -56,59 +69,89 @@ class ElasticsearchSettingsFormType extends AbstractType
->add('minScore', 'integer', [ ->add('minScore', 'integer', [
'label' => 'Thesaurus Min score', 'label' => 'Thesaurus Min score',
'constraints' => new Range(['min' => 0]), 'constraints' => new Range(['min' => 0]),
]); ])
->add('highlight', 'checkbox', [
'label' => 'Activate highlight',
'required' => false
])
// ->add('save', 'submit', [
// 'attr' => ['class' => 'btn btn-primary']
// ])
->add('esSettingFromIndex', 'button', [
'label' => 'Get setting form index',
'attr' => [
'onClick' => 'esSettingFromIndex()',
'class' => 'btn'
]
])
->add('dumpField', 'textarea', [
'label' => false,
'required' => false,
'mapped' => false,
'attr' => ['class' => 'dumpfield hide']
])
->add('activeTab', 'hidden');
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) { // keep aggregates in configuration order with this intermediate array
if(array_key_exists('choices', $f)) { $aggs = [];
// choices[] : choice_key => choice_value
$choices = $f['choices']; // helper fct to add aggregate to a tmp list
} $addAgg = function($k, $label, $help, $disabled=false, $choices=null) use (&$aggs) {
else { if(!$choices) {
$choices = [ $choices = [
"10 values" => 10, "10 values" => 10,
"20 values" => 20, "50 values" => 50,
"50 values" => 50, "100 values" => 100,
"100 values" => 100, "all values" => -1
"all values" => -1 ];
];
}
// array_unshift($choices, "not aggregated"); // always as first choice
$choices = array_merge(["not aggregated" => 0], $choices);
$builder
->add($k.'_limit', ChoiceType::class, [
// 'label' => $f['label'],// . ' ' . 'aggregate limit',
'choices_as_values' => true,
'choices' => $choices,
'attr' => [
'class' => 'aggregate'
]
]);
} }
$choices = array_merge(["not aggregated" => 0], $choices); // add this option always as first choice
$aggs[$k] = [ // default value will be replaced by hardcoded tech fields & all databoxes fields
'label' => $label,
'choices_as_values' => true,
'choices' => $choices,
'attr' => [
'class' => 'aggregate'
],
'disabled' => $disabled,
'help_message' => $help // todo : not displayed ?
];
};
$builder // all fields fron conf
->add('highlight', 'checkbox', [ foreach($this->esSettings->getAggregableFields() as $k=>$f) {
'label' => 'Activate highlight', // default value will be replaced by hardcoded tech fields & all databoxes fields
'required' => false $addAgg($k, "/?\\ " . $k, "This field does not exists in current databoxes.", true);
]) }
// ->add('save', 'submit', [
// 'attr' => ['class' => 'btn btn-primary'] // add or replace hardcoded tech fields
// ]) foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
->add('esSettingFromIndex', 'button', [ $choices = array_key_exists('choices', $f) ? $f['choices'] : null; // a tech-field can publish it's own choices
'label' => 'Get setting form index', $help = null;
'attr' => [ $label = '#' . $k;
'onClick' => 'esSettingFromIndex()', if(!array_key_exists($k, $aggs)) {
'class' => 'btn' $label = "/!\\ " . $label;
] $help = "New field, please confirm setting.";
]) }
->add('dumpField', 'textarea', [ $addAgg($k, $label, $help, false, $choices);
'label' => false, }
'required' => false,
'mapped' => false, // add or replace all databoxes fields (nb: new db field - unknown in conf - will be a the end)
'attr' => ['class' => 'dumpfield hide'] foreach($this->globalStructure->getAllFields() as $field) {
]) $k = $label = $field->getName();
->add('activeTab', 'hidden'); $help = null;
if(!array_key_exists($field->getName(), $aggs)) {
$label = "/!\\ " . $label;
$help = "New field, please confirm setting.";
}
$addAgg($k, $label, $help); // default choices
}
// populate aggs to form
foreach($aggs as $k=>$agg) {
$builder->add('facets:' . $k . ':limit', ChoiceType::class, $agg);
}
;
} }
public function getName() public function getName()

View File

@@ -15,7 +15,7 @@ class FacetsResponse
private $escaper; private $escaper;
private $facets = array(); private $facets = array();
public function __construct(Escaper $escaper, array $response, GlobalStructure $structure) public function __construct(ElasticsearchOptions $options, Escaper $escaper, array $response, GlobalStructure $structure)
{ {
$this->escaper = $escaper; $this->escaper = $escaper;
@@ -25,7 +25,13 @@ class FacetsResponse
$atf = ElasticsearchOptions::getAggregableTechnicalFields(); $atf = ElasticsearchOptions::getAggregableTechnicalFields();
foreach ($response['aggregations'] as $name => $aggregation) { // sort facets respecting the order defined in options
foreach($options->getAggregableFields() as $name=>$foptions) {
if(!array_key_exists($name, $response['aggregations'])) {
continue;
}
$aggregation = $response['aggregations'][$name];
$tf = null; $tf = null;
$valueFormatter = function($v){ return $v; }; // default equality formatter $valueFormatter = function($v){ return $v; }; // default equality formatter
@@ -78,6 +84,7 @@ class FacetsResponse
]; ];
} }
} }
} }

View File

@@ -35,11 +35,6 @@ final class GlobalStructure implements Structure
*/ */
private $private = array(); private $private = array();
/**
* @var Field[]
*/
private $facets = array();
/** /**
* @var Flag[] * @var Flag[]
*/ */
@@ -145,9 +140,11 @@ final class GlobalStructure implements Structure
$this->private[$name] = $field; $this->private[$name] = $field;
} }
/*
if ($field->isFacet() && $field->isSearchable()) { if ($field->isFacet() && $field->isSearchable()) {
$this->facets[$name] = $field; $this->facets[$name] = $field;
} }
*/
if ($field->hasConceptInference()) { if ($field->hasConceptInference()) {
$this->thesaurus_fields[$name] = $field; $this->thesaurus_fields[$name] = $field;
@@ -183,14 +180,6 @@ final class GlobalStructure implements Structure
return $this->private; return $this->private;
} }
/**
* @return Field[]
*/
public function getFacetFields()
{
return $this->facets;
}
/** /**
* @return Field[] * @return Field[]
*/ */

View File

@@ -47,14 +47,6 @@ final class LimitedStructure implements Structure
return $this->limit($this->structure->getPrivateFields()); return $this->limit($this->structure->getPrivateFields());
} }
/**
* @return Field[]
*/
public function getFacetFields()
{
return $this->limit($this->structure->getFacetFields());
}
public function getThesaurusEnabledFields() public function getThesaurusEnabledFields()
{ {
return $this->limit($this->structure->getThesaurusEnabledFields()); return $this->limit($this->structure->getThesaurusEnabledFields());

View File

@@ -33,11 +33,6 @@ interface Structure
*/ */
public function getPrivateFields(); public function getPrivateFields();
/**
* @return Field[]
*/
public function getFacetFields();
/** /**
* @return Field[] * @return Field[]
*/ */

View File

@@ -60,7 +60,7 @@ class BinariesRequirements extends RequirementCollection implements RequirementI
); );
} }
$exiftool = __DIR__ . '/../../../../../vendor/phpexiftool/exiftool/exiftool' . (defined('PHP_WINDOWS_VERSION_BUILD') ? '.exe' : ''); $exiftool = __DIR__ . '/../../../../../vendor/exiftool/exiftool/exiftool' . (defined('PHP_WINDOWS_VERSION_BUILD') ? '.exe' : '');
$this->addRequirement( $this->addRequirement(
is_file($exiftool) && is_executable($exiftool), is_file($exiftool) && is_executable($exiftool),

View File

@@ -39,7 +39,6 @@ class eventsmanager_broker
], ],
'notify' => [ 'notify' => [
'eventsmanager_notify_autoregister', 'eventsmanager_notify_autoregister',
'eventsmanager_notify_bridgeuploadfail',
'eventsmanager_notify_downloadmailfail', 'eventsmanager_notify_downloadmailfail',
'eventsmanager_notify_feed', 'eventsmanager_notify_feed',
'eventsmanager_notify_order', 'eventsmanager_notify_order',

View File

@@ -42,7 +42,7 @@ class eventsmanager_notify_order extends eventsmanager_notifyAbstract
$ret = [ $ret = [
'text' => $this->app->trans('%user% a passe une %opening_link% commande %end_link%', [ 'text' => $this->app->trans('%user% a passe une %opening_link% commande %end_link%', [
'%user%' => $sender, '%user%' => $sender,
'%opening_link%' => '<a href="/prod/order/'.$order_id.'/" class="dialog full-dialog" title="'.$this->app->trans('Orders manager').'">', '%opening_link%' => '<a href="#" class="order-notif" data-id="'.$order_id.'" title="'.$this->app->trans('Orders manager').'">',
'%end_link%' => '</a>',]) '%end_link%' => '</a>',])
, 'class' => '' , 'class' => ''
]; ];

View File

@@ -62,6 +62,16 @@ class patch_410alpha17a implements patchInterface
// $sql = "ALTER TABLE `metadatas_structure` ADD `gui_editable` INT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `readonly`"; // $sql = "ALTER TABLE `metadatas_structure` ADD `gui_editable` INT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `readonly`";
// $databox->get_connection()->executeQuery($sql); // $databox->get_connection()->executeQuery($sql);
foreach ($databox->get_meta_structure() as $databox_field) {
if ($databox_field->get_tbranch() != '') {
$databox_field->set_generate_cterms(true);
} else {
$databox_field->set_generate_cterms(false);
}
$databox_field->save();
}
return true; return true;
} }
} }

View File

@@ -0,0 +1,111 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2019 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
class patch_410alpha21a implements patchInterface
{
/** @var string */
private $release = '4.1.0-alpha.21a';
/** @var array */
private $concern = [base::DATA_BOX];
/**
* Returns the release version.
*
* @return string
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [];
}
/**
* {@inheritdoc}
*/
public function apply(base $databox, Application $app)
{
// fix the Longitude value
$sql = 'SELECT id, record_id, name, value FROM technical_datas WHERE trim(name) = "LongitudeRef" ';
$stmt = $databox->get_connection()->prepare($sql);
$stmt->execute();
$rs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
foreach ($rs as $row) {
if (trim($row['value']) === 'W' ) {
$sql = 'UPDATE technical_datas SET value = CONCAT("-", value) WHERE trim(name) = "Longitude" AND record_id =:record_id';
$stmt = $databox->get_connection()->prepare($sql);
$stmt->execute([':record_id' => $row['record_id']]);
}
$sqlDelete = 'DELETE FROM technical_datas WHERE id =:id';
$stmt1 = $databox->get_connection()->prepare($sqlDelete);
$stmt1->execute([':id' => $row['id']]);
$stmt1->closeCursor();
}
$stmt->closeCursor();
// fix the Latitude value
$sql = 'SELECT id, record_id, name, value FROM technical_datas WHERE trim(name) = "LatitudeRef" ';
$stmt = $databox->get_connection()->prepare($sql);
$stmt->execute();
$rs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
foreach ($rs as $row) {
if (trim($row['value']) === 'S' ) {
$sql = 'UPDATE technical_datas SET value = CONCAT("-", value) WHERE trim(name) = "Latitude" AND record_id =:record_id';
$stmt = $databox->get_connection()->prepare($sql);
$stmt->execute([':record_id' => $row['record_id']]);
}
$sqlDelete = 'DELETE FROM technical_datas WHERE id =:id';
$stmt1 = $databox->get_connection()->prepare($sqlDelete);
$stmt1->execute([':id' => $row['id']]);
$stmt1->closeCursor();
}
$stmt->closeCursor();
return true;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2019 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
class patch_410alpha22a implements patchInterface
{
/** @var string */
private $release = '4.1.0-alpha.22a';
/** @var array */
private $concern = [base::APPLICATION_BOX];
/**
* Returns the release version.
*
* @return string
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [];
}
/**
* {@inheritdoc}
*/
public function apply(base $appbox, Application $app)
{
foreach(['type', 'created', 'updated', 'expiration'] as $t) {
$sql = "ALTER TABLE `Tokens` ADD INDEX `".$t."` (`".$t."`);";
try {
$stmt = $appbox->get_connection()->prepare($sql);
$stmt->execute();
$stmt->closeCursor();
}
catch (\Exception $e) {
// the inex already exists ?
}
}
return true;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2019 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Alchemy\Phrasea\Application;
class patch_410alpha23a implements patchInterface
{
/** @var string */
private $release = '4.1.0-alpha.23a';
/** @var array */
private $concern = [base::APPLICATION_BOX];
/**
* Returns the release version.
*
* @return string
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [];
}
/**
* {@inheritdoc}
*/
public function apply(base $appbox, Application $app)
{
// fix embed-bundle keys
if ($app['conf']->has(['embed_bundle', 'video', 'available-speeds'])) {
$availableSpeed = $app['conf']->get(['embed_bundle', 'video', 'available-speeds']);
$app['conf']->remove(['embed_bundle', 'video', 'available-speeds']);
$app['conf']->set(['embed_bundle', 'video', 'available_speeds'], $availableSpeed);
}
if ($app['conf']->has(['embed_bundle', 'audio', 'available-speeds'])) {
$availableSpeed = $app['conf']->get(['embed_bundle', 'audio', 'available-speeds']);
$app['conf']->remove(['embed_bundle', 'audio', 'available-speeds']);
$app['conf']->set(['embed_bundle', 'audio', 'available_speeds'], $availableSpeed);
}
if ($app['conf']->has(['embed_bundle', 'document', 'enable-pdfjs'])) {
$enablePdfjs = $app['conf']->get(['embed_bundle', 'document', 'enable-pdfjs']);
$app['conf']->remove(['embed_bundle', 'document', 'enable-pdfjs']);
$app['conf']->set(['embed_bundle', 'document', 'enable_pdfjs'], $enablePdfjs);
}
// geoloc section change replace 'name' to 'map-provider'
if ($app['conf']->has(['geocoding-providers', 0, 'name'])) {
$geocodingName = $app['conf']->get(['geocoding-providers', 0, 'name']);
$app['conf']->remove(['geocoding-providers', 0, 'name']);
$app['conf']->set(['geocoding-providers', 0, 'map-provider'], $geocodingName);
}
// video-editor section change, replace 'vttFieldName' to 'ChapterVttFieldName'
if ($app['conf']->has(['video-editor', 'vttFieldName'])) {
$chapterVttFieldName = $app['conf']->get(['video-editor', 'vttFieldName']);
$app['conf']->remove(['video-editor', 'vttFieldName']);
$app['conf']->set(['video-editor', 'ChapterVttFieldName'], $chapterVttFieldName);
}
// remove registry classic section if exist
if ($app['conf']->has(['registry', 'classic'])) {
$app['conf']->remove(['registry', 'classic']);
}
// remove bridge section if exist
if ($app['conf']->has(['main', 'bridge'])) {
$app['conf']->remove(['main', 'bridge']);
}
// insert RGPD bloc if not exist
if (!$app['conf']->has(['user_account', 'deleting_policies', 'email_confirmation'])) {
$app['conf']->set(['user_account', 'deleting_policies', 'email_confirmation'], true);
}
return true;
}
}

View File

@@ -1722,17 +1722,20 @@ class record_adapter implements RecordInterface, cache_cacheableInterface
} }
/** /**
* @param int $offset
* @param null|int $max_items
*
* @return set_selection|record_adapter[] * @return set_selection|record_adapter[]
* @throws Exception * @throws Exception
* @throws \Doctrine\DBAL\DBALException * @throws \Doctrine\DBAL\DBALException
*/ */
public function getChildren() public function getChildren($offset = 1, $max_items = null)
{ {
if (!$this->isStory()) { if (!$this->isStory()) {
throw new Exception('This record is not a grouping'); throw new Exception('This record is not a grouping');
} }
$selections = $this->getDatabox()->getRecordRepository()->findChildren([$this->getRecordId()]); $selections = $this->getDatabox()->getRecordRepository()->findChildren([$this->getRecordId()], null, $offset, $max_items);
return reset($selections); return reset($selections);
} }

View File

@@ -39,21 +39,37 @@ main:
populate_order: RECORD_ID populate_order: RECORD_ID
populate_direction: DESC populate_direction: DESC
activeTab: '#elastic-search' activeTab: '#elastic-search'
base_aggregate_limit: 10 facets:
collection_aggregate_limit: 10 _base:
doctype_aggregate_limit: 0 limit: 10
camera_model_aggregate_limit: 0 _collection:
iso_aggregate_limit: 0 limit: 10
aperture_aggregate_limit: 0 _doctype:
shutterspeed_aggregate_limit: 0 limit: 10
flashfired_aggregate_limit: 0 _camera_model:
framerate_aggregate_limit: 0 limit: 0
audiosamplerate_aggregate_limit: 0 _iso:
videocodec_aggregate_limit: 0 limit: 0
audiocodec_aggregate_limit: 0 _aperture:
orientation_aggregate_limit: 0 limit: 0
colorspace_aggregate_limit: 0 _shutterspeed:
mimetype_aggregate_limit: 0 limit: 0
_flashfired:
limit: 0
_framerate:
limit: 0
_audiosamplerate:
limit: 0
_videocodec:
limit: 0
_audiocodec:
limit: 0
_orientation:
limit: 0
_colorspace:
limit: 0
_mimetype:
limit: 0
task-manager: task-manager:
status: started status: started
enabled: true enabled: true
@@ -87,6 +103,7 @@ main:
mp4box_timeout: 60 mp4box_timeout: 60
swftools_timeout: 60 swftools_timeout: 60
unoconv_timeout: 60 unoconv_timeout: 60
exiftool_timeout: 60
storage: storage:
subdefs: null subdefs: null
cache: null cache: null
@@ -94,6 +111,7 @@ main:
download: null download: null
lazaret: null lazaret: null
caption: null caption: null
tmp_files: null
trusted-proxies: [] trusted-proxies: []
debugger: debugger:
@@ -241,7 +259,7 @@ embed_bundle:
geocoding-providers: geocoding-providers:
- -
map-provider: 'mapboxWebGL' map-provider: 'mapboxWebGL'
enabled: true enabled: false
public-key: '' public-key: ''
map-layers: map-layers:
- -
@@ -284,7 +302,7 @@ geocoding-providers:
provincefields: Province provincefields: Province
countryfields: 'Country, Pays' countryfields: 'Country, Pays'
video-editor: video-editor:
vttFieldName: VideoTextTrackChapters ChapterVttFieldName: VideoTextTrackChapters
seekBackwardStep: 500 # in ms seekBackwardStep: 500 # in ms
seekForwardStep: 500 # in ms seekForwardStep: 500 # in ms
playbackRates: playbackRates:

View File

@@ -65,7 +65,7 @@
"normalize-css": "^2.1.0", "normalize-css": "^2.1.0",
"npm": "^6.0.0", "npm": "^6.0.0",
"npm-modernizr": "^2.8.3", "npm-modernizr": "^2.8.3",
"phraseanet-production-client": "^0.34.86-d", "phraseanet-production-client": "0.34.139-d",
"requirejs": "^2.3.5", "requirejs": "^2.3.5",
"tinymce": "^4.0.28", "tinymce": "^4.0.28",
"underscore": "^1.8.3", "underscore": "^1.8.3",

View File

@@ -66,5 +66,5 @@
- name: Make exiftool executable - name: Make exiftool executable
file: file:
path: /vagrant/vendor/phpexiftool/exiftool/exiftool path: /vagrant/vendor/exiftool/exiftool/exiftool
mode: 0755 mode: 0755

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2019-11-19T08:48:53Z" source-language="en" target-language="de" datatype="plaintext" original="not.available"> <file date="2020-02-20T14:37:38Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<header> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note> <note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2019-11-19T08:49:17Z" source-language="en" target-language="en" datatype="plaintext" original="not.available"> <file date="2020-02-20T14:37:51Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<header> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note> <note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2019-11-15T08:03:23Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available"> <file date="2020-02-20T14:38:04Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<header> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note> <note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2"> <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2019-11-19T08:50:13Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available"> <file date="2020-02-20T14:38:19Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<header> <header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/> <tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note> <note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -17,3 +17,157 @@ $mainMenuMarginBottom: 30px;
#mainContent { #mainContent {
padding-top: $mainMenuHeight; padding-top: $mainMenuHeight;
} }
/*Help menu*/
.contextMenu.helpcontextmenu {
display: none;
&.shown {
display: block;
position: absolute;
z-index: 101;
}
}
.help-trigger {
display:inline-block;
cursor:pointer;
padding: 0 4px;
border:none;
margin-left: -12px;
}
.phraseanet_logo {
background: url(../../common/images/logo_phraseanet.png) no-repeat center center /contain;
height: 75px;
width: 230px;
display: inline-block;
}
.phraseanet_version {
font-size: 15px;
font-family: Roboto;
font-weight: 500;
color: #b7b7b7;
padding-left: 58px;
a {
color: #b7b7b7;
&:hover {
color: #FFFFFF;
}
}
}
.phraseanet_about_top {
background: #4e4e4e;
padding: 30px 20px;
}
.phraseanet_about_bottom {
padding: 25px 20px;
min-height: 150px;
background: #c8c8c8;
a {
font-family: Roboto;
line-height: 1.2;
font-size: 15px!important;
font-weight: 500;
color: #000000!important;
text-decoration: underline;
}
}
.phraseanet_gpl {
img {
position: absolute;
width: 84px;
}
span {
padding-top: 26px;
display: inline-block;
padding-left: 100px;
padding-bottom: 20px;
text-decoration: underline;
}
a {
text-decoration: underline;
}
}
.ui-dialog {
position: absolute;
top: 0;
left: 0;
padding: .2em;
outline: 0;
}
.ui-dialog .ui-dialog-titlebar {
padding: .4em 1em;
position: relative;
}
.ui-dialog .ui-dialog-title {
float: left;
margin: .1em 0;
white-space: nowrap;
width: 90%;
overflow: hidden;
text-overflow: ellipsis;
}
.ui-dialog .ui-dialog-titlebar-close {
position: absolute;
right: .3em;
top: 50%;
width: 21px;
margin: -10px 0 0 0;
padding: 1px;
height: 20px;
text-indent: -9999999px;
}
.ui-dialog .ui-dialog-content {
position: relative;
border: 0;
padding: .5em 1em;
background: none;
overflow: auto;
}
.ui-dialog .ui-dialog-buttonpane {
text-align: left;
border-width: 1px 0 0 0;
background-image: none;
margin-top: .5em;
padding: .3em 1em .5em .4em;
}
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
float: right;
}
.ui-dialog .ui-dialog-buttonpane button {
margin: .5em .4em .5em 0;
cursor: pointer;
}
.overlay,
.ui-widget-overlay {
background-color: #000;
opacity: 0.7;
filter: alpha(opacity=70);
height: 100%;
width: 100%
}
.ui-icon, .ui-widget-content .ui-icon {
background-image: url(/assets/vendors/jquery-ui/images/ui-lightness/ui-icons_222222_256x240.png)!important;
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
background-position: -96px -128px;
width: 16px;
height: 16px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -8px;
margin-top: -8px;
}

View File

@@ -325,6 +325,7 @@ div.switch_right.unchecked {
.ui-dialog-titlebar-close { .ui-dialog-titlebar-close {
border: none; border: none;
margin-right: 4px; margin-right: 4px;
text-indent: -9999999px;
} }
.ui-icon.ui-icon-closethick { .ui-icon.ui-icon-closethick {
background-image: url("/assets/common/images/icons/cross-white.png"); background-image: url("/assets/common/images/icons/cross-white.png");
@@ -841,6 +842,76 @@ span.simplecolorpicker.picker {
} }
} }
.help-trigger {
display:inline-block;
cursor:pointer;
padding: 0 4px;
border:none;
margin-left: -12px;
}
.phraseanet_logo {
background: url(../../common/images/logo_phraseanet.png) no-repeat center center /contain;
height: 75px;
width: 230px;
display: inline-block;
}
.phraseanet_version {
font-size: 15px;
font-family: Roboto;
font-weight: 500;
color: #b7b7b7;
padding-left: 58px;
a {
color: #b7b7b7;
&:hover {
color: #FFFFFF;
}
}
}
.phraseanet_about_top {
background: #4e4e4e;
padding: 30px 20px;
}
.phraseanet_about_bottom {
padding: 25px 20px;
min-height: 150px;
background: #c8c8c8;
a {
font-family: Roboto;
line-height: 1.2;
font-size: 15px!important;
font-weight: 500;
color: #000000!important;
text-decoration: underline;
}
}
.phraseanet_gpl {
img {
position: absolute;
width: 84px;
}
span {
padding-top: 26px;
display: inline-block;
padding-left: 100px;
padding-bottom: 20px;
text-decoration: underline;
}
a {
text-decoration: underline;
}
}
.ui-icon, .ui-widget-content .ui-icon {
background-image: url(/assets/vendors/jquery-ui/images/ui-lightness/ui-icons_222222_256x240.png)!important;
}
.ui-dialog-titlebar-close {
text-indent: -9999999px;
}
@import './databases'; @import './databases';
@import './fields'; @import './fields';
@import './tables'; @import './tables';

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -94,7 +94,7 @@ var commonModule = (function ($, p4) {
.empty() .empty()
.append(el.attr('infos')) .append(el.attr('infos'))
.dialog({ .dialog({
title: 'About',
autoOpen: false, autoOpen: false,
closeOnEscape: true, closeOnEscape: true,
resizable: false, resizable: false,
@@ -106,7 +106,7 @@ var commonModule = (function ($, p4) {
backgroundColor: '#000', backgroundColor: '#000',
opacity: 0.7 opacity: 0.7
} }
}).dialog('open').css({'overflow-x': 'auto', 'overflow-y': 'auto'}); }).dialog('open').css({'overflow-x': 'auto', 'overflow-y': 'hidden', 'padding': '0'});
} }

View File

@@ -107,3 +107,60 @@ $mainMenuLinkBackgroundHoverColor: transparent;
margin-left: -12px; margin-left: -12px;
} }
.phraseanet_logo {
background: url(../images/logo_phraseanet.png) no-repeat center center /contain;
height: 75px;
width: 230px;
display: inline-block;
}
.phraseanet_version {
font-size: 15px;
font-family: Roboto;
font-weight: 500;
color: #b7b7b7;
padding-left: 58px;
a {
color: #b7b7b7;
&:hover {
color: #FFFFFF;
}
}
}
.phraseanet_about_top {
background: #4e4e4e;
padding: 30px 20px;
}
.phraseanet_about_bottom {
padding: 25px 20px;
min-height: 150px;
background: #c8c8c8;
a {
font-family: Roboto;
line-height: 1.2;
font-size: 15px!important;
font-weight: 500;
color: #000000!important;
text-decoration: underline;
}
}
.phraseanet_gpl {
img {
position: absolute;
width: 84px;
}
span {
padding-top: 26px;
display: inline-block;
padding-left: 100px;
padding-bottom: 20px;
text-decoration: underline;
}
a {
text-decoration: underline;
}
}

View File

@@ -319,3 +319,8 @@ a.active_choice {
color: #313131; color: #313131;
padding: 6px 0 6px 30px; padding: 6px 0 6px 30px;
} }
.pdf-iframe {
background: #1a1a1a;
padding-bottom: 10px;
}

View File

@@ -650,8 +650,12 @@ button.confirm_report {
min-height: 26px; min-height: 26px;
padding: 10px 0; padding: 10px 0;
} }
.ui-controlgroup, fieldset.ui-controlgroup {
margin: 10px 0!important;
}
.ui-listview.lightbox-list-view { .ui-listview.lightbox-list-view {
margin-top: 0;
margin-bottom: 0;
& > .ui-li-static { & > .ui-li-static {
padding: .7em 1em; padding: .7em 1em;
text-overflow: inherit; text-overflow: inherit;
@@ -687,6 +691,13 @@ button.confirm_report {
.center-image { .center-image {
text-align: center; text-align: center;
iframe {
min-height: 240px;
width: auto;
.ui-mobile & {
width: 100%;
}
}
} }
/*validate page*/ /*validate page*/

View File

@@ -360,3 +360,86 @@ $select-height: 26px;
#mainMenu li .context-menu-item-inner a:hover { #mainMenu li .context-menu-item-inner a:hover {
color: #000; color: #000;
} }
.ui-dialog-titlebar-close {
text-indent: -9999999px;
}
/*Help menu*/
.contextMenu.helpcontextmenu {
display: none;
&.shown {
display: block;
position: absolute;
z-index: 101;
}
}
.help-trigger {
display:inline-block;
cursor:pointer;
padding: 0 4px;
border:none;
margin-left: -12px;
}
.phraseanet_logo {
background: url(../../common/images/logo_phraseanet.png) no-repeat center center /contain;
height: 75px;
width: 230px;
display: inline-block;
}
.phraseanet_version {
font-size: 15px;
font-family: Roboto;
font-weight: 500;
color: #b7b7b7;
padding-left: 58px;
a {
color: #b7b7b7;
&:hover {
color: #FFFFFF;
}
}
}
.phraseanet_about_top {
background: #4e4e4e;
padding: 30px 20px;
}
.phraseanet_about_bottom {
padding: 25px 20px;
min-height: 150px;
background: #c8c8c8;
a {
font-family: Roboto;
line-height: 1.2;
font-size: 15px!important;
font-weight: 500;
color: #000000!important;
text-decoration: underline;
}
}
.phraseanet_gpl {
img {
position: absolute;
width: 84px;
}
span {
padding-top: 26px;
display: inline-block;
padding-left: 100px;
padding-bottom: 20px;
text-decoration: underline;
}
a {
text-decoration: underline;
}
}
.ui-icon, .ui-widget-content .ui-icon {
background-image: url(/assets/vendors/jquery-ui/images/ui-lightness/ui-icons_222222_256x240.png)!important;
}

View File

@@ -57,9 +57,13 @@
{% set url = app.getAuthenticator().isAuthenticated() ? thumbnail.get_url() : thumbnail.get_permalink().get_url() %} {% set url = app.getAuthenticator().isAuthenticated() ? thumbnail.get_url() : thumbnail.get_permalink().get_url() %}
{% set record_type = thumbnail.get_type() %} {% set record_type = thumbnail.get_type() %}
{% if record_type == 'VIDEO_MP4' or record_type == 'VIDEO_FLV' %} {% if record_type == 'VIDEO_MP4' or record_type == 'VIDEO_FLV' %}
{% set thumbnail_height = thumbnail.get_height() > 0 ? thumbnail.get_height() : 120 %}
{% set thumbnail_width = thumbnail.get_width() > 0 ? thumbnail.get_width() : 120 %}
<input type="hidden" class="hidden" id="videoHeight" name="videoHeight" value="{{ thumbnail_height }}"/>
<input type="hidden" class="hidden" id="videoWidth" name="videoWidth" value="{{ thumbnail_width }}"/>
<iframe width="100%" height="100%" <iframe width="100%" height="100%"
src="{{ url('alchemy_embed_view', {url: url|trim, autoplay: autoplay|default('false') }) }}" src="{{ url('alchemy_embed_view', {url: url|trim, autoplay: autoplay|default('false') }) }}"
frameborder="0" allowfullscreen></iframe> frameborder="0" allowfullscreen class="video-iframe"></iframe>
{% elseif record_type == 'FLEXPAPER' %} {% elseif record_type == 'FLEXPAPER' %}
<iframe width="100%" height="100%" <iframe width="100%" height="100%"
src="{{ url('alchemy_embed_view', {url: url|trim, autoplay: autoplay|default('false') }) }}" src="{{ url('alchemy_embed_view', {url: url|trim, autoplay: autoplay|default('false') }) }}"
@@ -68,6 +72,8 @@
<iframe width="100%" height="100%" <iframe width="100%" height="100%"
src="{{ url('alchemy_embed_view', {url: url|trim, autoplay: autoplay|default('false') }) }}" src="{{ url('alchemy_embed_view', {url: url|trim, autoplay: autoplay|default('false') }) }}"
frameborder="0" allowfullscreen></iframe> frameborder="0" allowfullscreen></iframe>
{% elseif record_type == 'PDF' %}
<iframe src="{{ url('alchemy_embed_view', {url: url|trim }) }}" width="100%" scrolling="no" marginheight="0" frameborder="0" allowfullscreen="" height="0" class="pdf-iframe"></iframe>
{% else %} {% else %}
<img style="max-height: 100%;max-width:100%" class="record record_image imgTips zoomable thumb" <img style="max-height: 100%;max-width:100%" class="record record_image imgTips zoomable thumb"
oncontextMenu="return(false);" oncontextMenu="return(false);"

View File

@@ -77,8 +77,8 @@
for="radio-view-no_{{ basket_element.getId() }}">{{ 'validation:: NON' | trans }}</label> for="radio-view-no_{{ basket_element.getId() }}">{{ 'validation:: NON' | trans }}</label>
</fieldset> </fieldset>
{% endif %} {% endif %}
<div style="text-align:center;margin:0 0 1em 0"> <div style="text-align:center;margin:20px 0">
<a href="{{ path('lightbox_ajax_note_form', { 'sselcont_id' : basket_element.getId() }) }}" data-role="button" data-inline="true" data-rel="dialog" data-transition="slidedown"> <a style="margin: 0" href="{{ path('lightbox_ajax_note_form', { 'sselcont_id' : basket_element.getId() }) }}" data-role="button" data-inline="true" data-rel="dialog" data-transition="slidedown">
{{ 'validation:: editer ma note' | trans }} {{ 'validation:: editer ma note' | trans }}
</a> </a>
</div> </div>
@@ -93,5 +93,4 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -107,7 +107,7 @@
<div class="control-group"> <div class="control-group">
<label class="form_label control-label" for="form_function"><strong>{{ "admin::compte-utilisateur poste" | trans }}</strong></label> <label class="form_label control-label" for="form_function"><strong>{{ "admin::compte-utilisateur poste" | trans }}</strong></label>
<div class="controls"> <div class="controls">
<input class="input_element input-xlarge" type="text" name="form_function" id="form_function" value="{{ app["authentication"].getUser().getActivity() }}" /> <input class="input_element input-xlarge" type="text" name="form_function" id="form_function" value="{{ app["authentication"].getUser().getJob() }}" />
<p class="form_alert help-block"></p> <p class="form_alert help-block"></p>
</div> </div>
</div> </div>
@@ -121,7 +121,7 @@
<div class="control-group"> <div class="control-group">
<label class="form_label control-label" for="form_activity"><strong>{{ "admin::compte-utilisateur activite" | trans }}</strong></label> <label class="form_label control-label" for="form_activity"><strong>{{ "admin::compte-utilisateur activite" | trans }}</strong></label>
<div class="controls"> <div class="controls">
<input class="input_element input-xlarge" type="text" name="form_activity" id="form_activity" value="{{ app["authentication"].getUser().getJob() }}" /> <input class="input_element input-xlarge" type="text" name="form_activity" id="form_activity" value="{{ app["authentication"].getUser().getActivity() }}" />
<p class="form_alert help-block"></p> <p class="form_alert help-block"></p>
</div> </div>
</div> </div>

View File

@@ -27,3 +27,17 @@
{# bootstrap admin field backbone application #} {# bootstrap admin field backbone application #}
<script type="text/javascript" src="{{ path('minifier', { 'f' : '/scripts/apps/admin/fields/main.js' }) }}"></script> <script type="text/javascript" src="{{ path('minifier', { 'f' : '/scripts/apps/admin/fields/main.js' }) }}"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#admin-field-app').on('change', '#tbranch', function () {
if ($(this).val() === '') {
$('#generate_cterms').prop('checked', false);
$('.generate-cterms').addClass('hidden');
} else {
$('.generate-cterms').removeClass('hidden');
}
return false;
});
});
</script>

View File

@@ -238,14 +238,12 @@
<label for="aggregable">{% trans %}Aggregation{% endtrans %}</label> <label for="aggregable">{% trans %}Aggregation{% endtrans %}</label>
</td> </td>
<td> <td>
<select id="aggregable"> <%= field['aggregable'] == "0" ? '{% trans %}Not aggregated{% endtrans %}' : '' %>
<option <%= field['aggregable'] == "0" ? 'selected' : '' %> value='0'>{% trans %}Not aggregated{% endtrans %}</option> <%= field['aggregable'] == "10" ? '10 values' : '' %>
<option <%= field['aggregable'] == "10" ? 'selected' : '' %> value='10'>10 values</option> <%= field['aggregable'] == "20" ? '20 values' : '' %>
<option <%= field['aggregable'] == "20" ? 'selected' : '' %> value='20'>20 values</option> <%= field['aggregable'] == "50" ? '50 values' : '' %>
<option <%= field['aggregable'] == "50" ? 'selected' : '' %> value='50'>50 values</option> <%= field['aggregable'] == "100" ? '100 values' : '' %>
<option <%= field['aggregable'] == "100" ? 'selected' : '' %> value='100'>100 values</option> <%= field['aggregable'] == "-1" ? 'All values' : '' %>
<option <%= field['aggregable'] == "-1" ? 'selected' : '' %> value='-1'>{% trans %}All values{% endtrans %}</option>
</select>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -261,11 +259,14 @@
</tr> </tr>
<tr> <tr>
<td><label for="tbranch">{% trans %}Thesaurus branch{% endtrans %}</label></td> <td><label for="tbranch">{% trans %}Thesaurus branch{% endtrans %}</label></td>
<td><input id="tbranch" type="text" value="<%= field.tbranch %>"/></td> <td>
</tr> <input id="tbranch" type="text" value="<%= field.tbranch %>"/>
<tr> <div style="display: inline-block;" <%= (field.tbranch == "") ? "class='generate-cterms hidden'" : "class='generate-cterms'" %> >
<td><label for="generate_cterms" class="checkbox">{% trans %}Generate-cterms{% endtrans %}</label></td> <label for="generate_cterms" class="checkbox">
<td><input id="generate_cterms" type="checkbox" <%= field.generate_cterms ? "checked='checked'" : "" %> /></td> <input id="generate_cterms" type="checkbox" <%= field.generate_cterms ? "checked='checked'" : "" %> />
{% trans %}Generate-cterms{% endtrans %}</label>
</div>
</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@@ -1,12 +1,37 @@
<div class="general-aggregation-layout"> <div class="general-aggregation-layout">
<button type="submit" id="elasticsearch_settings_save_facets" name="elasticsearch_settings[save]"
class="btn btn-primary">{{ 'Save' | trans }}</button>
<div id="basket-filter" class="facet-filter unstyled" style="float: right; padding-top: 27px;">
{#<span class="filter-title">{{ 'See' | trans }} : </span>#}
<span class="filter-item">
<label id="aggregated-list" class="checkbox inline" for="basketFilter">
<input type="checkbox" class="checkbox " value="aggregated" checked
style="margin-top: 1px">
{{ 'Aggregated' | trans }}
</label>
</span>
<span class="filter-item" style="margin-left: 10px">
<label id="not-aggregated-list" class="checkbox inline" for="basketFilter">
<input type="checkbox" class="checkbox " value="not-aggregated" checked
style="margin-top: 1px">
{{ 'Not aggregated' | trans }}
</label>
</span>
</div>
<ul class="unstyled aggregation-collection"> <ul class="unstyled aggregation-collection">
{% for formdata in form %} {% for formdata in form %}
{% set attr = formdata.vars['attr']|join(',') %} {% set attr = formdata.vars['attr']|join(',') %}
{% set label = formdata.vars['label']|join(',') %}
{% if attr == 'aggregate' %} {% if attr == 'aggregate' %}
<li> <li id="{{ label }}" class="field-row {% if label starts with '#_' %}lightblue{% endif %}">
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td class="handle">
<i class="fa fa-arrows" aria-hidden="true"></i>
</td>
<td> <td>
{{ form_label(formdata, null, { {{ form_label(formdata, null, {
'label_attr': {'class': 'label-aggregation'} 'label_attr': {'class': 'label-aggregation'}
@@ -22,6 +47,39 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
<button type="submit" id="elasticsearch_settings_save" name="elasticsearch_settings[save]" <input type="hidden" id="listValues" name="facet_list_values"/>
class="btn btn-primary">{{ 'Save' | trans }}</button>
</div> </div>
<script type="text/javascript">
/*Add a sort for the list*/
function filterFacet() {
$.each($(".aggregate option:selected"), function () {
if ($(this).val() == 0) {
$(this).closest('.field-row').addClass('not-aggregated')
}
else {
$(this).closest('.field-row').addClass('aggregated')
}
});
$('#aggregated-list input').click(function () {
$('.aggregated').toggleClass('hidden');
});
$('#not-aggregated-list input').click(function () {
$('.not-aggregated').toggleClass('hidden');
});
}
/*Get the order of new list*/
var sortEventHandler = function (event, ui) {
$('#listValues').val(JSON.stringify($('form').serializeArray()));
};
$(document).ready(function () {
$('.lightblue .label-aggregation').css('color','mediumblue');
$('.aggregation-collection').sortable({
handle: ".handle",
stop: sortEventHandler
});
// sortEventHandler(null, null); // enforce build list
filterFacet();
$("#elasticsearch_settings_save_facets").click(function(){sortEventHandler(null, null);});
});
</script>

View File

@@ -186,8 +186,8 @@
<li class="menu-bar-item"> <li class="menu-bar-item">
<a href="#"> <a href="#">
<img src="/assets/common/images/icons/menu-help.png"/> <img src="/assets/common/images/icons/menu-help.png"/>
<span style="" class="infoDialog" <span class="context-menu-item-inner infoDialog"
infos="<div><span style='font-size:18px;'>PHRASEANET</span> {{ app['phraseanet.version'].getName() }} (V{{ app['phraseanet.version'].getNumber() }})</div><div></div><br/><div><a href='http://www.gnu.org/licenses/gpl.html' target='_blank'><img src='http://www.gnu.org/graphics/gplv3-88x31.png' style='vertical-align:middle;'/><span>License GNU GPL v3</span></a></div><br/><div><a href='http://www.phraseanet.com/' target='_blank'> &copy; Copyright Alchemy 2005-{{ "now"|date("Y") }}</a><p style='margin-top: 10px' ><a href='../../gitlog.txt' target='_blank'>gitlog</a></p></div>">{{ 'phraseanet:: a propos' | trans }}</span> infos="<div class='phraseanet_about'><div class='phraseanet_about_top'><span class='phraseanet_logo'></span><div class='phraseanet_version'> {{ app['phraseanet.version'].getName() }} (V{{ app['phraseanet.version'].getNumber() }}) &nbsp; <a href='../../gitlog.txt' target='_blank'>gitlog</a></div></div><div class='phraseanet_about_bottom'><div class='phraseanet_gpl'><a href='http://www.gnu.org/licenses/gpl.html' target='_blank'><img src='/assets/common/images/GPLv3_Logo.png' width='380' /><span>License GNU GPL v3</span></a></div><p class='phraseanet_copyright'><a href='http://www.phraseanet.com/' target='_blank'> &copy; Copyright Alchemy 2005-{{ "now"|date("Y") }}</a></p></div></div>">{{ 'phraseanet:: a propos' | trans }}</span>
</a> </a>
</li> </li>
</ol> </ol>
@@ -252,7 +252,7 @@
{% endif %} {% endif %}
<div title="" class="context-menu-item menu3-custom-item"> <div title="" class="context-menu-item menu3-custom-item">
<div style="" class="context-menu-item-inner infoDialog" <div style="" class="context-menu-item-inner infoDialog"
infos="<div><span style='font-size:18px;'>PHRASEANET</span> {{ app['phraseanet.version'].getName() }} (V{{ app['phraseanet.version'].getNumber() }})</div><div></div><br/><div><a href='http://www.gnu.org/licenses/gpl.html' target='_blank'><img src='http://www.gnu.org/graphics/gplv3-88x31.png' style='vertical-align:middle;'/><span>License GNU GPL v3</span></a></div><br/><div><a href='http://www.phraseanet.com/' target='_blank'> &copy; Copyright Alchemy 2005-{{ "now"|date("Y") }}</a><p style='margin-top: 10px' ><a href='../../gitlog.txt' target='_blank'>gitlog</a></p></div>">{{ 'phraseanet:: a propos' | trans }}</div> infos="<div class='phraseanet_about'><div class='phraseanet_about_top'><span class='phraseanet_logo'></span><div class='phraseanet_version'> {{ app['phraseanet.version'].getName() }} (V{{ app['phraseanet.version'].getNumber() }}) &nbsp; <a href='../../gitlog.txt' target='_blank'>gitlog</a></div></div><div class='phraseanet_about_bottom'><div class='phraseanet_gpl'><a href='http://www.gnu.org/licenses/gpl.html' target='_blank'><img src='/assets/common/images/GPLv3_Logo.png' width='380' /><span>License GNU GPL v3</span></a></div><p class='phraseanet_copyright'><a href='http://www.phraseanet.com/' target='_blank'> &copy; Copyright Alchemy 2005-{{ "now"|date("Y") }}</a></p></div></div>">{{ 'phraseanet:: a propos' | trans }}</div>
</div> </div>
</div> </div>
</td> </td>

View File

@@ -15,7 +15,11 @@
{% set previewHtml5 = null %} {% set previewHtml5 = null %}
{% if app.getAclForUser(app.getAuthenticatedUser()).has_access_to_subdef(record, 'preview') and record.has_preview == true %} {# first check if type audio and there is no preview , fallback to thumbnail #}
{% if app.getAclForUser(app.getAuthenticatedUser()).has_access_to_subdef(record, 'preview') and record.getType == 'audio' and record.has_preview == false %}
{% set preview_obj = record.get_thumbnail() %}
{% elseif app.getAclForUser(app.getAuthenticatedUser()).has_access_to_subdef(record, 'preview') %}
{% set preview_obj = record.get_preview() %} {% set preview_obj = record.get_preview() %}
{% else %} {% else %}
{% set preview_obj = record.get_thumbnail() %} {% set preview_obj = record.get_thumbnail() %}

View File

@@ -45,7 +45,7 @@
{% endmacro %} {% endmacro %}
{% block content_account %} {% block content_account %}
<form id="form_create" action="{{ path("submit_developers_application") }}" method="POST" class="form-horizontal"> <form id="form_create" action="{{ path("submit_developers_application") }}" method="POST" class="form-horizontal" style="padding: 20px; background: #757575;">
{% if form is none %} {% if form is none %}
{% set name, description, website, callback = "", "", "", "" %} {% set name, description, website, callback = "", "", "", "" %}
{% set app_type = "web" %} {% set app_type = "web" %}
@@ -74,7 +74,7 @@
<div class="control-group"> <div class="control-group">
<label for="website" class="control-label"><b>{{ "Site web" | trans }}</b></label> <label for="website" class="control-label"><b>{{ "Site web" | trans }}</b></label>
<div class="controls"> <div class="controls">
<select name="scheme-website" class="input-mini"> <select name="scheme-website" class="input-mini" style="min-height: 40px;">
<option value="http://">http://</option> <option value="http://">http://</option>
<option value="https://">https://</option> <option value="https://">https://</option>
</select> </select>
@@ -101,7 +101,7 @@
<div class="control-group callback-control-group"> <div class="control-group callback-control-group">
<label for="callback" class="control-label"><b>{{ "URL de callback" | trans }}</b></label> <label for="callback" class="control-label"><b>{{ "URL de callback" | trans }}</b></label>
<div class="controls"> <div class="controls">
<select name="scheme-callback" class="input-mini"> <select name="scheme-callback" class="input-mini" style="min-height: 40px;">
<option value="http://">http://</option> <option value="http://">http://</option>
<option value="https://">https://</option> <option value="https://">https://</option>
</select> </select>
@@ -110,14 +110,18 @@
</div> </div>
{% endif %} {% endif %}
<div class="form-actions"> <div class="control-group">
<button class="btn btn-info" type="submit"> <div class="col-md-6 controls" style="margin-right: 25px; float: left">
{{ "boutton::valider" | trans }} <a class="btn btn-warning" href="{{ path("developers_applications") }}">
</button>
<a class="btn" href="{{ path("developers_applications") }}">
{{ "boutton::retour" | trans }} {{ "boutton::retour" | trans }}
</a> </a>
</div>
<div class="col-md-6" style="width: 50%; float: left">
<button class="btn btn-info no-margin" type="submit" style="margin: 0;width: auto;min-width: 200px">
{{ "boutton::valider" | trans }}
</button>
</div>
</div> </div>
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -66,4 +66,25 @@
</div> </div>
</div> </div>
</div> </div>
<script>
$('a.delete-app').bind('click', function (e) {
e.preventDefault();
var $this = $(this);
var li = $this.closest('li');
$.ajax({
type: 'DELETE',
url: $this.attr('href'),
dataType: 'json',
data: {},
success: function success(data) {
if (data.success) {
li.find('.modal').modal('hide');
li.remove();
}
}
});
});
</script>
{% endblock %} {% endblock %}

View File

@@ -20,7 +20,7 @@
<table id="main_wrapper" cellspacing="0" cellpadding="0"> <table id="main_wrapper" cellspacing="0" cellpadding="0">
<thead> <thead>
<tr> <tr>
<th class="title"> <th class="title" id="feed_list">
<h1>{{ 'Validations' | trans }}</h1> <h1>{{ 'Validations' | trans }}</h1>
</th> </th>
<th> <th>
@@ -29,10 +29,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for basket in baskets_collection %} {% for basket in baskets_collection | sort | reverse%}
{% if basket.getValidation() %} {% if basket.getValidation() %}
{% set basket_length = basket.getElements().count() %} {% set basket_length = basket.getElements().count() %}
<tr> {% set counter = ( counter | default(0) ) + 1 %}
<tr class="{% if counter >=4 %}other_feed hidden{% endif %}">
<td colspan="2"> <td colspan="2">
<div class="basket_wrapper ui-corner-all clickable"> <div class="basket_wrapper ui-corner-all clickable">
<table cellspacing="0" cellpadding="0" border="0"> <table cellspacing="0" cellpadding="0" border="0">
@@ -84,20 +86,26 @@
</div> </div>
</td> </td>
</tr> </tr>
{% if counter == 3 %}<tr><td colspan="2" style="text-align: center"><a href="#see_more_feed" id="see_more_feed" class="see_more_feed btn btn-info">{{ 'lightbox::See_more_feedback' | trans }}</a></td></tr>{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<tr><td colspan="2" style="text-align: center"><a href="#feed_list" id="see_less_feed" class="see_more_feed hidden btn btn-success">{{ 'lightbox::See_less_feedback' | trans }}</a></td></tr>
<tr> <tr>
<th class="title"> <th class="title" id="panier_list">
<h1>{{ 'Paniers' | trans }}</h1> <h1>{{ 'Paniers' | trans }}</h1>
</th> </th>
<th> <th>
<i>{{ 'Voici vos paniers' | trans }}</i> <i>{{ 'Voici vos paniers' | trans }}</i>
</th> </th>
</tr> </tr>
{% for basket in baskets_collection %} {% for basket in baskets_collection | sort | reverse%}
{% if basket.getValidation is null %} {% if basket.getValidation is null %}
{% set basket_length = basket.getElements().count() %} {% set basket_length = basket.getElements().count() %}
<tr> {% set counter = ( counter | default(0) ) + 1 %}
{% set counter_length = baskets_collection.length() %}
<tr class="{% if counter >=4 %}other_basket hidden{% endif %}">
<td colspan="2"> <td colspan="2">
<div class="basket_wrapper ui-corner-all clickable"> <div class="basket_wrapper ui-corner-all clickable">
<table cellspacing="0" cellpadding="0" border="0"> <table cellspacing="0" cellpadding="0" border="0">
@@ -140,9 +148,14 @@
<input type="hidden" name="ssel_id" value="{{ basket.getId() }}"/> <input type="hidden" name="ssel_id" value="{{ basket.getId() }}"/>
</div> </div>
</td> </td>
</tr> </tr>
{% if counter == 3 %}<tr><td colspan="2" style="text-align: center"><a href="#see_more_basket" id="see_more_basket" class="see_more_basket btn btn-info">{{ 'lightbox::see_more_basket' | trans }}</a></td></tr>{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<tr><td colspan="2" style="text-align: center"><a href="#panier_list" id="see_less_basket" class="see_more_basket hidden btn btn-success">{{ 'lightbox::see_less_basket' | trans }}</a></td></tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@@ -1,6 +1,11 @@
{% extends 'prod/Tooltip/Tooltip.html.twig' %} {% extends 'prod/Tooltip/Tooltip.html.twig' %}
{% set title %}{{ 'Caption' | trans }}{% endset %} {% set title %}{{ 'Caption' | trans }} &nbsp;<span class="collection-block" style="float: right"> {% set collectionLogo = collection_logo(record.baseId) %}
{% if collectionLogo is empty %}
{{ record.collectionName }}
{% else %}
{{ collectionLogo|raw }}
{% endif %}</span> {% endset %}
{% set width = 400 %} {% set width = 400 %}
{% set maxwidth = 700 %} {% set maxwidth = 700 %}

View File

@@ -1,6 +1,6 @@
{# designed to be printed in a small box #} {# designed to be printed in a small box #}
<div class="PNB10" callback="{{ callback }}"> <div class="PNB10" callback="{{ callback }}">
<form id="quickAddUser" method="POST" action="{{ path('prod_push_do_add_user') }}"> <form id="quickAddUser" method="POST" action="{{ path('prod_push_do_add_user') }}" style="padding-top: 20px">
<table> <table>
<tr> <tr>
<td> <td>
@@ -23,16 +23,6 @@
<input name="job" placeholder="{{ 'Job' | trans }}" type="text" value=""/> <input name="job" placeholder="{{ 'Job' | trans }}" type="text" value=""/>
</td> </td>
</tr> </tr>
<tr>
<td>
<input name="city" placeholder="{{ 'City' | trans }}" type="text" value="" class="geoname_field"
autocomplete="false"/>
</td>
<td>
<input name="pays" placeholder="{{ 'Pays' | trans }}" type="text" value="" class="geoname_field"
autocomplete="false"/>
</td>
</tr>
<tr> <tr>
<td colspan="2" style="text-align: center;padding: 10px 0px;"> <td colspan="2" style="text-align: center;padding: 10px 0px;">
<button class="cancel" type="button">{{ 'Cancel' | trans }}</button> <button class="cancel" type="button">{{ 'Cancel' | trans }}</button>

View File

@@ -89,7 +89,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div style="margin:0 7px;overflow:hidden;position:relative"> <div class="basket-content">
{% set basket_length = basket.getElements()|length %} {% set basket_length = basket.getElements()|length %}
<div class="alert_datas_changed ui-corner-all">{{ 'Certaines donnees du panier ont change' | trans }} <a class="basket_refresher" href="#">{{ 'rafraichir' | trans }}</a></div> <div class="alert_datas_changed ui-corner-all">{{ 'Certaines donnees du panier ont change' | trans }} <a class="basket_refresher" href="#">{{ 'rafraichir' | trans }}</a></div>
{% if basket_length == 0 %} {% if basket_length == 0 %}

View File

@@ -5,8 +5,37 @@
</a> </a>
</div> </div>
<div class="bloc"> <div class="bloc">
<div id="basket-filter" class="basket-filter">
<span class="filter-title">{{ 'See' | trans }} : </span>
<span class="filter-item">
<label id="basket-list" class="checkbox inline" for="basketFilter">
<input type="checkbox" class="checkbox " name="basketFilter" value="basket" checked>
{{ 'Basket' | trans }}
</label>
</span>
<span class="filter-item">
<label id="feedback-list" class="checkbox inline" for="basketFilter">
<input type="checkbox" class="checkbox " name="basketFilter" value="feedback" checked>
{{ 'Feedback' | trans }}
</label>
</span>
<span class="filter-item">
<label id="push-list" class="checkbox inline" for="basketFilter">
<input type="checkbox" class="checkbox " name="basketFilter" value="push" checked >
{{ 'Push' | trans }}
</label>
</span>
<span class="filter-item">
<label id="story-list" class="checkbox inline" for="basketFilter">
<input type="checkbox" class="checkbox " name="basketFilter" value="story" checked>
{{ 'Stories' | trans }}
</label>
</span>
</div>
<div class="insidebloc"> <div class="insidebloc">
{% set content = WorkZone.getContent(srt) %} {% set content = WorkZone.getContent(srt) %}
<div id="validations-block" class="validations-block">
{% for basket in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::VALIDATIONS')) %} {% for basket in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::VALIDATIONS')) %}
<div tooltipsrc="{{ path('prod_tooltip_basket', { 'basket' : basket.getId() }) }}" <div tooltipsrc="{{ path('prod_tooltip_basket', { 'basket' : basket.getId() }) }}"
id="SSTT_{{basket.getId()}}" id="SSTT_{{basket.getId()}}"
@@ -31,8 +60,11 @@
class="basket_title" class="basket_title"
src="/assets/common/images/icons/valid.png" /> src="/assets/common/images/icons/valid.png" />
</td> </td>
<td> <td class="contextMenuWrapper">
<a class="contextMenuTrigger icon-display-grid" href="#"></a> <a class="contextMenuTrigger" href="#">
<i class="icomoon icon-circle fa-stack-2x fa-inverse-bg" aria-hidden="true"></i>
<i class="icomoon icon-round-more_horiz-24px white fa-stack-1x fa-inverse" aria-hidden="true"></i>
</a>
</td> </td>
</tr> </tr>
</table> </table>
@@ -78,13 +110,14 @@
</div> </div>
<div id="SSTT_content_{{basket.getId()}}" class="content basket" style="overflow:hidden;"></div> <div id="SSTT_content_{{basket.getId()}}" class="content basket" style="overflow:hidden;"></div>
{% endfor %} {% endfor %}
</div>
<div id="all_baskets-block" class="all_baskets-block">
{% for basket in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::BASKETS')) %} {% for basket in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::BASKETS')) %}
<div tooltipsrc="{{ path('prod_tooltip_basket', { 'basket' : basket.getId() }) }}" <div tooltipsrc="{{ path('prod_tooltip_basket', { 'basket' : basket.getId() }) }}"
id="SSTT_{{basket.getId()}}" id="SSTT_{{basket.getId()}}"
class="basketTips ui-accordion-header ui-state-default class="basketTips ui-accordion-header ui-state-default
ui-corner-all header SSTT basket {% if not basket.isRead() %}unread{% endif %} ui-corner-all header SSTT basket {% if not basket.isRead() %}unread{% endif %}
{% if basket.getId() == selected_id and selected_type == 'basket' %}active{% endif %}"> {% if basket.getId() == selected_id and selected_type == 'basket' %}active{% endif %}{% if basket.getValidation() %}feedbacks-block{% elseif basket.getPusher() %}pushes-block{% else %}baskets-block{% endif %} ">
<a class="workzone-menu-title" href="{{ path('prod_baskets_basket', { 'basket' : basket.getId() }) }}" style=""> <a class="workzone-menu-title" href="{{ path('prod_baskets_basket', { 'basket' : basket.getId() }) }}" style="">
<span> <span>
{% if basket.getValidation() %} {% if basket.getValidation() %}
@@ -125,8 +158,10 @@
</td> </td>
{% endif %} {% endif %}
--> -->
<td> <td class="contextMenuWrapper">
<a class="contextMenuTrigger icon-display-grid" href="#"></a> <a class="contextMenuTrigger" href="#">
<i class="icomoon icon-circle fa-stack-2x fa-inverse-bg" aria-hidden="true"></i>
<i class="icomoon icon-round-more_horiz-24px white fa-stack-1x fa-inverse" aria-hidden="true"></i></a>
</td> </td>
</tr> </tr>
</table> </table>
@@ -209,9 +244,8 @@
</div> </div>
<div id="SSTT_content_{{basket.getId()}}" class="content basket" style="overflow:hidden;"> </div> <div id="SSTT_content_{{basket.getId()}}" class="content basket" style="overflow:hidden;"> </div>
{% endfor %} {% endfor %}
</div>
<div id="stories-block" class="stories-block">
{% for story in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::STORIES')) %} {% for story in content.get(constant('\\Alchemy\\Phrasea\\Helper\\WorkZone::STORIES')) %}
<div tooltipsrc="{{ path('prod_tooltip_story', { 'sbas_id' : story.getRecord(app).get_sbas_id(), 'record_id' : story.getRecord(app).get_record_id() }) }}" <div tooltipsrc="{{ path('prod_tooltip_story', { 'sbas_id' : story.getRecord(app).get_sbas_id(), 'record_id' : story.getRecord(app).get_record_id() }) }}"
id="SSWZ_{{story.getId()}}" sbas="{{story.getSbasId()}}" id="SSWZ_{{story.getId()}}" sbas="{{story.getSbasId()}}"
@@ -228,8 +262,11 @@
<div class="menu"> <div class="menu">
<table> <table>
<tr> <tr>
<td> <td class="contextMenuWrapper">
<a class="contextMenuTrigger icon-display-grid" href="#"></a> <a class="contextMenuTrigger " href="#">
<i class="icomoon icon-circle fa-stack-2x fa-inverse-bg" aria-hidden="true"></i>
<i class="icomoon icon-round-more_horiz-24px white fa-stack-1x fa-inverse" aria-hidden="true"></i>
</a>
</td> </td>
</tr> </tr>
</table> </table>
@@ -291,6 +328,7 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
{% endmacro %} {% endmacro %}
@@ -304,49 +342,56 @@
{% endif %} {% endif %}
{% import 'common/thumbnail.html.twig' as thumbnail %} {% import 'common/thumbnail.html.twig' as thumbnail %}
<div class="chim-content">
{% if app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_title_display') == '1' %}
<div class="title">
{{ record.get_title()|truncate(20, "...") }}
</div>
{% endif %}
{% if app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_status_display') == '1' %}
<div class="status" style="position:relative;height:20px;overflow-y:visible;z-index:15;">
{% for flag in record_flags(record) %}
<img src="{{ flag.path }}" title="{{ attribute(flag.labels, app.locale) }}" />
{% endfor %}
</div>
{% endif %}
<div class="CHIM diapo CHIM_{{record.get_serialize_key()}} open-preview-action"
data-kind="{% if wz_scope == 'groupings' %}REG{% else %}BASK{% endif %}"
data-position="{{ ord }}"
data-id="{% if wz_scope == 'groupings' %}{{container.get_sbas_id()}}_{{container.get_record_id()}}{% else %}{{container.getId()}}{% endif %}"
style="height:{{box_height}}px;"
id="CHIM_{% if wz_scope == 'groupings' %}{{record.get_serialize_key()}}{% else %}{{ contained.getId() }}{% endif %}">
<input type="hidden" name="id" value="{{ record.get_serialize_key() }}"/>
<div class="CHIM diapo CHIM_{{record.get_serialize_key()}} open-preview-action" <div class="record-number">
data-kind="{% if wz_scope == 'groupings' %}REG{% else %}BASK{% endif %}" {{record.getNumber()}}
data-position="{{ ord }}" </div>
data-id="{% if wz_scope == 'groupings' %}{{container.get_sbas_id()}}_{{container.get_record_id()}}{% else %}{{container.getId()}}{% endif %}" {{thumbnail.format(record.get_thumbnail,140,140, '', true, false)}}
style="height:{{box_height}}px;" <div class="bottom">
id="CHIM_{% if wz_scope == 'groupings' %}{{record.get_serialize_key()}}{% else %}{{ contained.getId() }}{% endif %}"> {% if app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_caption_display') == '1' %}
<input type="hidden" name="id" value="{{ record.get_serialize_key() }}"/> <span class="icon-stack captionRolloverTips captionTips"
{% if app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_title_display') == '1' %} tooltipsrc="{{ path('prod_tooltip_caption', { 'sbas_id' : record.get_sbas_id(), 'record_id' : record.get_record_id(), 'context' : 'basket', 'number' : record.getNumber() }) }}">
<div class="title"> <i class="icomoon icon-round-list-24px white" ></i>
{{record.get_title()}} </span>
</div> {% endif %}
{% endif %} <a
{% if app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_status_display') == '1' %} {% if wz_scope == 'groupings' %}
<div class="status" style="position:relative;height:20px;overflow-y:visible;z-index:15;"> id="WZEL{{ container.get_sbas_id() }}_{{ container.get_record_id() }}_{{ record.get_sbas_id() }}_{{ record.get_record_id() }}"
{% for flag in record_flags(record) %} href="{{ path('prod_stories_story_remove_element', { 'sbas_id' : container.get_sbas_id(), 'record_id' : container.get_record_id(), 'child_sbas_id' : record.get_sbas_id(), 'child_record_id' : record.get_record_id() }) }}"
<img src="{{ flag.path }}" title="{{ attribute(flag.labels, app.locale) }}" /> {% elseif wz_scope == 'basket' %}
{% endfor %} id="WZEL_{{ container.getId() }}_{{ record.get_sbas_id() }}_{{ record.get_record_id() }}"
</div> href="{{ path('prod_baskets_basket_element_remove', { 'basket' : container.getId(), 'basket_element_id' : contained.getId()}) }}"
{% endif %} {% elseif wz_scope == 'basket' %}
<div style="position:absolute;background-color:#959595;color:black;font-weight:bold;padding:3px;z-index:90;"> href="#"
{{record.getNumber()}} {% endif %}
</div> class="WorkZoneElementRemover {{ wz_scope }}" title="{{ 'delete' | trans }}" >
{{thumbnail.format(record.get_thumbnail,82,82, '', true, false)}} <i class="icomoon icon-circle fa-stack-2x" aria-hidden="true"></i>
<div class="bottom"> <i class="icomoon icon-round-close-24px fa-stack-1x " aria-hidden="true"></i>
<a </a>
{% if wz_scope == 'groupings' %}
id="WZEL{{ container.get_sbas_id() }}_{{ container.get_record_id() }}_{{ record.get_sbas_id() }}_{{ record.get_record_id() }}" </div>
href="{{ path('prod_stories_story_remove_element', { 'sbas_id' : container.get_sbas_id(), 'record_id' : container.get_record_id(), 'child_sbas_id' : record.get_sbas_id(), 'child_record_id' : record.get_record_id() }) }}" </div>
{% elseif wz_scope == 'basket' %} </div>
id="WZEL_{{ container.getId() }}_{{ record.get_sbas_id() }}_{{ record.get_record_id() }}"
href="{{ path('prod_baskets_basket_element_remove', { 'basket' : container.getId(), 'basket_element_id' : contained.getId()}) }}"
{% elseif wz_scope == 'basket' %}
href="#"
{% endif %}
class="WorkZoneElementRemover {{ wz_scope }}" title="{{ 'delete' | trans }}" >
<i class="fa fa-times" aria-hidden="true"></i>
</a>
{% if app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_caption_display') == '1' %}
<div class="captionRolloverTips" tooltipsrc="{{ path('prod_tooltip_caption', { 'sbas_id' : record.get_sbas_id(), 'record_id' : record.get_record_id(), 'context' : 'basket', 'number' : record.getNumber() }) }}"></div>
{% endif %}
</div>
</div>
{% endmacro %} {% endmacro %}
{% macro display_basket(app, basket) %} {% macro display_basket(app, basket) %}

View File

@@ -1,5 +1,5 @@
<div> <div>
<button class="tools-accordion"><span>Actions<i class="fa fa-chevron-right rotate"></i></span></button> <button class="tools-accordion"><span>Actions</span></button>
<div class="tools-panel"> <div class="tools-panel">
<button class="ui-corner-all TOOL_disktt_btn story_window" data-selection-source="story"> <button class="ui-corner-all TOOL_disktt_btn story_window" data-selection-source="story">
@@ -81,7 +81,7 @@
</div> </div>
</div> </div>
<div style="margin:0 7px;overflow:hidden;position: relative"> <div class="basket-content">
{% set story_length = Story.get_children().get_elements()|length %} {% set story_length = Story.get_children().get_elements()|length %}
<div class="alert_datas_changed ui-corner-all"> <div class="alert_datas_changed ui-corner-all">
{{ 'Certaines donnees du reportage ont change' | trans }} {{ 'Certaines donnees du reportage ont change' | trans }}
@@ -97,7 +97,7 @@
{% import 'prod/WorkZone/Macros.html.twig' as Macros %} {% import 'prod/WorkZone/Macros.html.twig' as Macros %}
{% for record in Story.get_children().get_elements() %} {% for record in Story.get_children().get_elements() %}
<span class="wrapCHIM_{{ record.get_serialize_key() }}"> <span class="chim-wrapper wrapCHIM_{{ record.get_serialize_key() }}">
{{ Macros.element('groupings', Story, record, record, record.getNumber()) }} {{ Macros.element('groupings', Story, record, record, record.getNumber()) }}
</span> </span>
{% endfor %} {% endfor %}

View File

@@ -115,7 +115,9 @@
<div class="frame_canva"> <div class="frame_canva">
<div id="thumb_delete_button"/> <div id="thumb_delete_button"/>
<div id="thumb_download_button"/> <div id="thumb_download_button"/>
<canvas id="thumb_canvas"></canvas> <div class="canvas-wrap">
<canvas id="thumb_canvas"></canvas>
</div>
<div id="alt_canvas_container" <div id="alt_canvas_container"
style="position:absolute;overflow:hidden;top:-1200px;visibility: hidden; width:0!important;height:0!important"> style="position:absolute;overflow:hidden;top:-1200px;visibility: hidden; width:0!important;height:0!important">
{% for subdef in outputFormats %} {% for subdef in outputFormats %}

View File

@@ -41,7 +41,7 @@
<div style='position:absolute; top:0px; left:0px; height:20px'> <div style='position:absolute; top:0px; left:0px; height:20px'>
<img class="require_alert" src="/assets/common/images/icons/alert.png" style="display:none;cursor:help;" title="{{ 'edit::Certains champs doivent etre remplis pour valider cet editing' | trans }}"> <img class="require_alert" src="/assets/common/images/icons/alert.png" style="display:none;cursor:help;" title="{{ 'edit::Certains champs doivent etre remplis pour valider cet editing' | trans }}">
</div> </div>
<div style='position:absolute; bottom:0px; left:0px; height:20px' class="bottom actions"> <div style='position:absolute; bottom:7px; left:8px; height:20px' class="bottom actions">
<span class="fa-stack previewTips" <span class="fa-stack previewTips"
tooltipsrc="{{ path('prod_tooltip_preview', { 'sbas_id' : record.get_sbas_id(), 'record_id' : record.get_record_id() }) }}"> tooltipsrc="{{ path('prod_tooltip_preview', { 'sbas_id' : record.get_sbas_id(), 'record_id' : record.get_record_id() }) }}">
@@ -73,9 +73,9 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="editDiaButtons" style="position:absolute; bottom:0px; right:0px; width:30px; height:12px; display:none"> <div class="editDiaButtons" style="position:absolute; bottom:8px; right:3px; width:30px; display:none">
<img id="idEditDiaButtonsP_{{i}}" style="cursor:pointer" src="/assets/common/images/icons/plus11.png"/> <span id="idEditDiaButtonsP_{{i}}" style="cursor:pointer"><i class="fa fa-plus editIcon">&nbsp;</i></span>
<img id="idEditDiaButtonsM_{{i}}" style="cursor:pointer" src="/assets/common/images/icons/minus11.png"/> <span id="idEditDiaButtonsM_{{i}}" style="cursor:pointer"><i class="fa fa-minus editIcon">&nbsp;</i></span>
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
@@ -97,7 +97,7 @@
{#<i id="editSGtri_{{ i }}" style="visibility:hidden;" class="fa fa-caret-right" aria-hidden="true"></i>#} {#<i id="editSGtri_{{ i }}" style="visibility:hidden;" class="fa fa-caret-right" aria-hidden="true"></i>#}
<span class="fa-stack fieldTips" <span class="fa-stack fieldTips"
tooltipsrc="{{ path('prod_tooltip_metadata', { 'sbas_id' : field.get_databox().get_sbas_id(), 'field_id' : field.get_id() }) }}"> tooltipsrc="{{ path('prod_tooltip_metadata', { 'sbas_id' : field.get_databox().get_sbas_id(), 'field_id' : field.get_id() }) }}">
<i class="fa fa-circle fa-stack-2x" aria-hidden="true"></i> <i class="fa fa-circle fa-stack-2x fa-inverse-bg" aria-hidden="true"></i>
<i class="fa fa-info fa-stack-1x fa-inverse" aria-hidden="true"></i> <i class="fa fa-info fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span> </span>
{% if field.get_dces_element %} {% if field.get_dces_element %}
@@ -126,6 +126,7 @@
{% set actionable = recordsRequest|length %} {% set actionable = recordsRequest|length %}
{% set not_actionable = recordsRequest.received|length - actionable %} {% set not_actionable = recordsRequest.received|length - actionable %}
{% set editing_top_box_height = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'editing_top_box') %}
<div id="EDIT_ALL"> <div id="EDIT_ALL">
@@ -135,8 +136,7 @@
<input style="font-size:2px; width:5px;" type="text" id="editFakefocus" /> <input style="font-size:2px; width:5px;" type="text" id="editFakefocus" />
</form> </form>
</div> </div>
<div id="EDIT_TOP" style="height:{{app['settings'].getUserSetting(app.getAuthenticatedUser(), 'editing_top_box') <div id="EDIT_TOP" style="height:{{editing_top_box_height}}%; min-height: {{ editing_top_box_height }}">
}}%;">
<div id="EDIT_MENU"> <div id="EDIT_MENU">
<div id="EDIT_ZOOMSLIDER" > <div id="EDIT_ZOOMSLIDER" >
</div> </div>
@@ -198,7 +198,7 @@
</div> </div>
<div style="position:absolute; top:4px; width:60px; right:6px; height:24px;"> <div style="position:absolute; top:4px; width:60px; right:6px; height:24px;">
<button type="submit" class="submit-multivalued"> <button type="submit" class="submit-multivalued">
<img id="EditButAddMultiValued" style="cursor:pointer" src="/assets/common/images/icons/plus16.png" /> <i id="EditButAddMultiValued" class="icon-round-add_box-24px icomoon" style="font-size: 24px;color: grey;">&nbsp;</i>
</button> </button>
</div> </div>
</form> </form>
@@ -411,12 +411,32 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
{% if multipleDataboxes == 1 %}
$(function() {
$('#EDITWINDOW').hide();
// a workaround for a flaw in the demo system (http://dev.jqueryui.com/ticket/4375), ignore!
$( "#dialog-edit-many-sbas:ui-dialog" ).dialog( "destroy" );
$( "#dialog-edit-many-sbas" ).dialog({
modal: true,
resizable:false,
buttons: {
Ok: function() {
$( this ).dialog( "close" );
}
}
});
$('#dialog-edit-many-sbas').on('dialogclose', function(event) {
location.reload();
});
});
{% else %}
{% set notActionableMsg = '' %} {% set notActionableMsg = '' %}
{% if not_actionable > 1 %} {% if not_actionable > 1 %}
{% set notActionableMsg = 'prod::editing: %not_actionable% documents ne peuvent etre edites car vos droits sont induffisants' | trans({'%not_actionable%' : not_actionable}) %} {% set notActionableMsg = 'prod::editing: %not_actionable% documents ne peuvent etre edites car vos droits sont induffisants' | trans({'%not_actionable%' : not_actionable}) %}
{% elseif not_actionable == 1 %} {% elseif not_actionable == 1 %}
{% set notActionableMsg = 'prod::editing: 1 document ne peut etre edite car vos droits sont induffisants' | trans | e('js')%} {% set notActionableMsg = 'prod::editing: 1 document ne peut etre edite car vos droits sont induffisants' | trans | e('js')%}
{% endif %} {% endif %}
var recordEditorConfig = { var recordEditorConfig = {
hasMultipleDatabases: {% if multipleDataboxes or recordsRequest|length == 0 %}true{% else %}false{% endif %}, hasMultipleDatabases: {% if multipleDataboxes or recordsRequest|length == 0 %}true{% else %}false{% endif %},
databoxId: {{ databox.get_sbas_id }}, databoxId: {{ databox.get_sbas_id }},
@@ -477,4 +497,6 @@
{% endfor %} {% endfor %}
] ]
}; };
{% endif %}
</script> </script>

View File

@@ -2,6 +2,21 @@
<form target="_blank" name="formprintpage" method="POST" action="{{ path('prod_printer_print') }}"> <form target="_blank" name="formprintpage" method="POST" action="{{ path('prod_printer_print') }}">
<div id="printBox"> <div id="printBox">
<div style="padding:10px;"> <div style="padding:10px;">
{% if basketFeedbackId %}
<h4>{{ 'phraseanet:: basket feedback' | trans }}</h4>
<input type="hidden" name="ssel" value="{{ basketFeedbackId }}" />
<div style="margin: 10px 0 20px 0; padding: 0 10px;">
<label for="RADI_PRE_FBO" class="radio">
<input type="radio" name="lay" checked value="feedbackOnly" id="RADI_PRE_FBO" />
{{ 'print:: basket feedback only' | trans }}
</label>
<label for="RADI_PRE_BF" class="radio">
<input type="radio" name="lay" value="feedback" id="RADI_PRE_BF" />
{{ 'print:: basket feedback' | trans }}
</label>
</div>
{% endif %}
{% if printer.get_count_actionable() > 0 %} {% if printer.get_count_actionable() > 0 %}
{% if printer.get_count_preview() > 0 %} {% if printer.get_count_preview() > 0 %}
<h4>{{ 'phraseanet:: preview' | trans }}</h4> <h4>{{ 'phraseanet:: preview' | trans }}</h4>
@@ -32,7 +47,7 @@
{{ 'print:: liste d\'imagettes' | trans }} {{ 'print:: liste d\'imagettes' | trans }}
</label> </label>
<label for="RADI_PRE_THUMGRI" class="radio"> <label for="RADI_PRE_THUMGRI" class="radio">
<input type="radio" name="lay" checked value="thumbnailGrid" id="RADI_PRE_THUMGRI" /> <input type="radio" name="lay" {% if not basketFeedbackId %} checked {% endif %} value="thumbnailGrid" id="RADI_PRE_THUMGRI" />
{{ 'print:: planche contact (mosaique)' | trans }} {{ 'print:: planche contact (mosaique)' | trans }}
</label> </label>
</div> </div>
@@ -52,3 +67,12 @@
</div> </div>
</div> </div>
</form> </form>
<script type="text/javascript">
$(document).ready(function() {
var $form = $("form[name=formprintpage]");
$form.bind('submit', function(){
$("#DIALOG").dialog('destroy');
});
});
</script>

View File

@@ -183,6 +183,55 @@
</div> </div>
{% if GV_thesaurus %} {% if GV_thesaurus %}
<div id="proposals" class="PNB thesaurus-from-facets-action"> <div id="proposals" class="PNB thesaurus-from-facets-action">
<div class="filter-facet-setting look_box_settings">
<div id="accordion">
<div class="card">
<div class="card-header" id="headingOne">
<h5 class="mb-0" style="margin-left: 10px">
<button class="btn btn-facet-option collapsed" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
{{ 'prod:workzone:facetstab:search_and_facets_sort_options' | trans }} <span class="icomoon">&#xE96C;</span>
</button>
</h5>
</div>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-body">
<div class="left-facet-filter facet-filter">
<h4>{{ 'index::advance_search: facet-order' | trans }}</h4>
<form class="form-inline">
{% set order_facet = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'order_facet') %}
<label class="select" for="orderFacet">
<select class="preferences-facet-order" name="orderFacet" style="margin-left: 5px">
<option {% if order_facet == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_BCT') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_BCT') }}">{{ 'index::advance_search: facet-tech-order' | trans }}</option>
<option {% if order_facet == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') }}">{{ 'Alphabetic asc' | trans }}</option>
<option {% if order_facet == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_DESC') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_DESC') }}">{{ 'Alphabetic desc' | trans }}</option>
</select>
</label>
</form>
</div>
<div class="right-facet-filter facet-filter">
<h4>{{ 'index::advance_search: facet-values-order' | trans }}</h4>
<form class="form-inline">
{% set facet_values_order = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'facet_values_order') %}
<label class="select" for="facetValuesOrder">
<select class="preferences-facet-values-order" name="facetValuesOrder">
<option {% if facet_values_order == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_HITS') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_HITS') }}">{{ 'index::advance_search: order-by-hits' | trans }}</option>
<option {% if facet_values_order == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_HITS_ASC') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_HITS_ASC') }}">{{ 'index::advance_search: order-by-hits-asc' | trans }}</option>
<option {% if facet_values_order == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') }}">{{ 'Alphabetic asc' | trans }}</option>
</select>
</label>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<button id="facets-back-btn" style="display:none;">back</button> <button id="facets-back-btn" style="display:none;">back</button>
</div> </div>
{% include 'prod/tab_thesaurus.html.twig' with {has_access_to_module: app.getAclForUser(app.getAuthenticatedUser()).has_access_to_module('thesaurus')} %} {% include 'prod/tab_thesaurus.html.twig' with {has_access_to_module: app.getAclForUser(app.getAuthenticatedUser()).has_access_to_module('thesaurus')} %}
@@ -326,6 +375,9 @@
</div> </div>
<div class="pull-right"> <div class="pull-right">
<input class="btn btn_lightgrey reset_button search-reset-action" style="display: none;" type="button" value="{{ 'Re-initialiser' | trans }}" /> <input class="btn btn_lightgrey reset_button search-reset-action" style="display: none;" type="button" value="{{ 'Re-initialiser' | trans }}" />
<span class="btn btn-info btn-lg reload-search hidden " id="reload-search">{{ 'help::help-search: relaunch search without filter' | trans }}</span>
</div> </div>
</div> </div>
@@ -811,30 +863,6 @@
</div> </div>
<div class="box"> <div class="box">
<h1>{{ 'index::advance_search: facet' | trans }}</h1> <h1>{{ 'index::advance_search: facet' | trans }}</h1>
<h1>{{ 'index::advance_search: facet-order' | trans }}</h1>
<form class="form-inline">
{% set order_facet = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'order_facet') %}
<label class="select" for="orderFacet">
<select class="preferences-facet-order" name="orderFacet">
<option {% if order_facet == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_BCT') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_BCT') }}">{{ 'index::advance_search: facet-tech-order' | trans }}</option>
<option {% if order_facet == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') }}">{{ 'Alphabetic asc' | trans }}</option>
</select>
</label>
</form>
<h1>{{ 'index::advance_search: facet-values-order' | trans }}</h1>
<form class="form-inline">
{% set facet_values_order = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'facet_values_order') %}
<label class="select" for="facetValuesOrder">
<select class="preferences-facet-values-order" name="facetValuesOrder">
<option {% if facet_values_order == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_HITS') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_BY_HITS') }}">{{ 'index::advance_search: order-by-hits' | trans }}</option>
<option {% if facet_values_order == constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') %} selected="selected" {% endif %}
value="{{ constant('Alchemy\\Phrasea\\Core\\Configuration\\DisplaySettingService::ORDER_ALPHA_ASC') }}">{{ 'Alphabetic asc' | trans }}</option>
</select>
</label>
</form>
{% set facetFilter = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'facet') %} {% set facetFilter = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'facet') %}
<label class="checkbox inline" for="ADVSRCH_FILTER_FACET" style="margin-bottom: 1em"> <label class="checkbox inline" for="ADVSRCH_FILTER_FACET" style="margin-bottom: 1em">
<input id="ADVSRCH_FILTER_FACET" type="checkbox" name="filter_facet" {% if facetFilter == 'true' %}checked="checked"{% endif %}> <input id="ADVSRCH_FILTER_FACET" type="checkbox" name="filter_facet" {% if facetFilter == 'true' %}checked="checked"{% endif %}>
@@ -926,7 +954,7 @@
{% set basket_status_display = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_status_display') %} {% set basket_status_display = app['settings'].getUserSetting(app.getAuthenticatedUser(), 'basket_status_display') %}
<label for="basket_status_display" class="checkbox"> <label for="basket_status_display" class="checkbox">
<input <input
name="basket_status_display" type="checkbox preferences-options-basket-status" class="checkbox" value="1" name="basket_status_display" type="checkbox" class="checkbox preferences-options-basket-status" value="1"
id="basket_status_display" {% if basket_status_display == '1' %}checked="checked"{% endif %} /> id="basket_status_display" {% if basket_status_display == '1' %}checked="checked"{% endif %} />
{{ 'Afficher les status' | trans }} {{ 'Afficher les status' | trans }}
</label> </label>

View File

@@ -30,63 +30,11 @@
<li><p>{{ 'help::help-section-bullet: search-in-a-specific-field' | trans }}</p></li> <li><p>{{ 'help::help-section-bullet: search-in-a-specific-field' | trans }}</p></li>
</ul> </ul>
</div> </div>
{#<h5>{{ 'La recherche s\'effectue grâce à la boîte de dialogue qui se trouve en haut à gauche de l\'écran.' | trans }}#} <br>
{#{{ 'Sachez que vous pouvez utiliser les opérateurs ou caractères spéciaux suivants :' | trans }}</h5>#} <p class="text-center">{{ 'help::help-search: OR' | trans }}</p>
{#<h5 style="border:#CCCCCC 2px solid">{{ '* , ? , ET , OU , SAUF , DANS , DERNIERS , TOUT (ou AND , OR , EXCEPT , LAST , ALL)' | trans }}</h5>#} <br>
<p class="text-center">
{#<h5>{{ 'Caractères de troncature' | trans }}</h5>#} <span class="btn btn-info btn-lg trigger-reload-search" onclick="jQuery('.reload-search').trigger('click');">{{ 'help::help-search: relaunch search without filter' | trans }}</span>
</p>
{#<table>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky">{{ 'auto*' | trans }}</kbd></td>#}
{#<td valign="top"> {{ 'retourne "automobile", "automate", "autoroute", ...' | trans }}</td>#}
{#</tr>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky">{{ 'dé?it' | trans }}</kbd></td>#}
{#<td valign="top"> {{ 'retourne "délit", "débit", ...' | trans }}</td>#}
{#</tr>#}
{#</table>#}
{#<h5>{{ 'Visualiser tous les enregistrements / les derniers enregistrements' | trans }}</h5>#}
{#<table>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky"><b>{{ 'TOUT' | trans }}</b></kbd></td>#}
{#<td valign="top">{{ 'retourne tous les enregistrements des collections selectionnees' | trans }}</td>#}
{#</tr>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky"><b>{{ 'LAST 20' | trans }}</b></kbd></td>#}
{#<td valign="top">{{ 'retourne les 20 derniers enregistrements archives dans les collections selectionnees' | trans }}</td>#}
{#</tr>#}
{#</table>#}
{#<h5>{{ 'Recherche multicritères' | trans }}</h5>#}
{#{{ 'Vous pouvez affiner votre recherche avec les opérateurs : ET, OU, SAUF ou DANS' | trans }}<br>#}
{#<table>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky">{{ 'sport' | trans }} <b>ET</b> {{ 'automobile' | trans }}</kbd></td>#}
{#<td valign="top">{{ 'retourne les documents comprenant les deux mots.' | trans }}</td>#}
{#</tr>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky">{{ 'journal OU jt' | trans }}</kbd></td>#}
{#<td valign="top">{{ 'retourne les documents comprenant un mot et/ou l\'autre.' | trans }}</td>#}
{#</tr>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky">{{ 'cannes SAUF festival' | trans }}</kbd></td>#}
{#<td valign="top">{{ 'retourne les documents comprenant cannes sans le mot festival.' | trans }}</td>#}
{#</tr>#}
{#<tr>#}
{#<td valign="top"><kbd class="ky">{{ 'thalassa DANS titre' | trans }}</kbd></td>#}
{#<td valign="top">{{ 'retourne les documents où le terme est au moins présent dans le titre, en évitant par exemple celles où le terme est uniquement cité dans la légende.' | trans }}</td>#}
{#</tr>#}
{#</table>#}
{#<center>#}
{#<h3 style="background-color:#CCCCCC; color:#000000">{{ 'Attention' | trans }}</h3>#}
{#<h4> {{ 'pour chercher une phrase contenant un des mots-clé ci-dessus, utilisez les guillemets :' | trans }}</h4>#}
{#<kbd class='tx'><i>"</i>{{ 'C dans l\'air' | trans }}<i>"</i></kbd>#}
{#, <kbd class='tx'><i>"</i>{{ 'Et Dieu créa la femme' | trans }}<i>"</i></kbd>#}
{#, <kbd class='tx'><i>"</i>{{ 'bijou en or' | trans }}<i>"</i></kbd>#}
{#, <kbd class='tx'><i>"</i>{{ 'tout le sport' | trans }}<i>"</i></kbd>#}
{#</center>#}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -104,7 +104,7 @@
{% if settings.show_context_menu %} {% if settings.show_context_menu %}
<span class="fa-stack contextMenuTrigger" id="contextTrigger_{{ record.id }}" <span class="fa-stack contextMenuTrigger" id="contextTrigger_{{ record.id }}"
tooltipsrc="{{ path('prod_tooltip_technical_data', { 'sbas_id' : record.databoxId, 'record_id' : record.recordId }) }}"> tooltipsrc="{{ path('prod_tooltip_technical_data', { 'sbas_id' : record.databoxId, 'record_id' : record.recordId }) }}">
<i class="icomoon icon-circle fa-stack-2x" aria-hidden="true"></i> <i class="icomoon icon-circle fa-stack-2x fa-inverse-bg" aria-hidden="true"></i>
<i class="icomoon icon-round-more_horiz-24px white fa-stack-1x fa-inverse" aria-hidden="true"></i> <i class="icomoon icon-round-more_horiz-24px white fa-stack-1x fa-inverse" aria-hidden="true"></i>
</span> </span>
<table cellspacing="0" cellpadding="0" style="display:none;" id="answerContext_{{record.id}}" class="contextMenu answercontextmenu"> <table cellspacing="0" cellpadding="0" style="display:none;" id="answerContext_{{record.id}}" class="contextMenu answercontextmenu">

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