Merge branch 'master' into PHRAS-2779_Account_enable-to_delete_application_in_developer-tab

This commit is contained in:
Nicolas Maillat
2020-02-18 16:27:19 +01:00
committed by GitHub
28 changed files with 504 additions and 212 deletions

13
composer.lock generated
View File

@@ -275,16 +275,16 @@
},
{
"name": "alchemy/mediavorus",
"version": "0.4.9",
"version": "0.4.10",
"source": {
"type": "git",
"url": "https://github.com/alchemy-fr/MediaVorus.git",
"reference": "1a96dc4142ff8474c11285cab9eab11df9683255"
"reference": "3e235eb1efb528aea2973c946f4bf47630b98985"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/alchemy-fr/MediaVorus/zipball/1a96dc4142ff8474c11285cab9eab11df9683255",
"reference": "1a96dc4142ff8474c11285cab9eab11df9683255",
"url": "https://api.github.com/repos/alchemy-fr/MediaVorus/zipball/3e235eb1efb528aea2973c946f4bf47630b98985",
"reference": "3e235eb1efb528aea2973c946f4bf47630b98985",
"shasum": ""
},
"require": {
@@ -300,6 +300,7 @@
},
"require-dev": {
"jms/serializer": "~0.12.0",
"phpunit/phpunit": "^4.0|^5.0",
"silex/silex": "~1.0",
"symfony/yaml": "~2.0"
},
@@ -333,7 +334,7 @@
"keywords": [
"metadata"
],
"time": "2019-01-22T11:23:34+00:00"
"time": "2020-02-18T13:37:45+00:00"
},
{
"name": "alchemy/oauth2php",
@@ -6168,7 +6169,7 @@
],
"description": "A WebProfiler for Silex",
"homepage": "http://silex.sensiolabs.org/",
"abandoned": true,
"abandoned": "symfony/web-profiler-bundle",
"time": "2016-01-10T11:39:13+00:00"
},
{

View File

@@ -28,7 +28,8 @@ main:
port: 11211
search-engine:
type: phrasea
options: []
options:
facets: []
task-manager:
status: started
enabled: true

View File

@@ -13,9 +13,11 @@ namespace Alchemy\Phrasea\Controller\Admin;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchSettingsFormType;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use databox_descriptionStructure;
class SearchEngineController extends Controller
{
@@ -31,7 +33,19 @@ class SearchEngineController extends Controller
$form->handleRequest($request);
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');
}
@@ -76,6 +90,16 @@ class SearchEngineController extends Controller
*/
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());
}
@@ -85,7 +109,10 @@ class SearchEngineController extends Controller
*/
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'),
]);
}

View File

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

View File

@@ -433,24 +433,15 @@ class QueryController extends Controller
// populates facets (aggregates)
$facets = [];
// $facetClauses = [];
foreach ($result->getFacets() as $facet) {
$facetName = $facet['name'];
if(array_key_exists($facetName, $fieldsInfosByName)) {
$f = $fieldsInfosByName[$facetName];
$facet['label'] = $f['trans_label'];
$facet['labels'] = $f['labels'];
$facet['type'] = strtoupper($f['type']) . "-AGGREGATE";
$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"))
->setPhone($request->request->get("form_phone"))
->setFax($request->request->get("form_fax"))
->setJob($request->request->get("form_activity"))
->setJob($request->request->get("form_function"))
->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"));
$service->updateAccount($command);

View File

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

View File

@@ -97,7 +97,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
});
$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;
@@ -228,7 +228,8 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
});
$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())) {
$options->setIndexName(strtolower(sprintf('phraseanet_%s', str_replace(

View File

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

View File

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

View File

@@ -15,7 +15,14 @@ use Doctrine\ORM\Mapping as ORM;
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")
*/
class Token

View File

@@ -22,7 +22,6 @@ use RandomLib\Generator;
class TokenManipulator implements ManipulatorInterface
{
const LETTERS_AND_NUMBERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const TYPE_FEED_ENTRY = 'FEED_ENTRY';
const TYPE_PASSWORD = 'password';
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());
}
/**
* 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 $data

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@
*/
namespace Alchemy\Phrasea\SearchEngine\Elastic;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -17,6 +18,18 @@ use Symfony\Component\Validator\Constraints\Range;
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)
{
$builder
@@ -56,36 +69,7 @@ class ElasticsearchSettingsFormType extends AbstractType
->add('minScore', 'integer', [
'label' => 'Thesaurus Min score',
'constraints' => new Range(['min' => 0]),
]);
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
if(array_key_exists('choices', $f)) {
// choices[] : choice_key => choice_value
$choices = $f['choices'];
}
else {
$choices = [
"10 values" => 10,
"20 values" => 20,
"50 values" => 50,
"100 values" => 100,
"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'
]
]);
}
$builder
])
->add('highlight', 'checkbox', [
'label' => 'Activate highlight',
'required' => false
@@ -108,7 +92,66 @@ class ElasticsearchSettingsFormType extends AbstractType
])
->add('activeTab', 'hidden');
;
// keep aggregates in configuration order with this intermediate array
$aggs = [];
// helper fct to add aggregate to a tmp list
$addAgg = function($k, $label, $help, $disabled=false, $choices=null) use (&$aggs) {
if(!$choices) {
$choices = [
"10 values" => 10,
"50 values" => 50,
"100 values" => 100,
"all values" => -1
];
}
$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 ?
];
};
// all fields fron conf
foreach($this->esSettings->getAggregableFields() as $k=>$f) {
// default value will be replaced by hardcoded tech fields & all databoxes fields
$addAgg($k, "/?\\ " . $k, "This field does not exists in current databoxes.", true);
}
// add or replace hardcoded tech fields
foreach(ElasticsearchOptions::getAggregableTechnicalFields() as $k => $f) {
$choices = array_key_exists('choices', $f) ? $f['choices'] : null; // a tech-field can publish it's own choices
$help = null;
$label = '#' . $k;
if(!array_key_exists($k, $aggs)) {
$label = "/!\\ " . $label;
$help = "New field, please confirm setting.";
}
$addAgg($k, $label, $help, false, $choices);
}
// add or replace all databoxes fields (nb: new db field - unknown in conf - will be a the end)
foreach($this->globalStructure->getAllFields() as $field) {
$k = $label = $field->getName();
$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()

View File

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

View File

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

View File

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

View File

@@ -11,10 +11,10 @@
use Alchemy\Phrasea\Application;
class patch_410alpha20a implements patchInterface
class patch_410alpha21a implements patchInterface
{
/** @var string */
private $release = '4.1.0-alpha.20a';
private $release = '4.1.0-alpha.21a';
/** @var array */
private $concern = [base::DATA_BOX];

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

@@ -107,7 +107,7 @@
<div class="control-group">
<label class="form_label control-label" for="form_function"><strong>{{ "admin::compte-utilisateur poste" | trans }}</strong></label>
<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>
</div>
</div>
@@ -121,7 +121,7 @@
<div class="control-group">
<label class="form_label control-label" for="form_activity"><strong>{{ "admin::compte-utilisateur activite" | trans }}</strong></label>
<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>
</div>
</div>

View File

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

View File

@@ -1,12 +1,37 @@
<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">
{% for formdata in form %}
{% set attr = formdata.vars['attr']|join(',') %}
{% set label = formdata.vars['label']|join(',') %}
{% if attr == 'aggregate' %}
<li>
<li id="{{ label }}" class="field-row {% if label starts with '#_' %}lightblue{% endif %}">
<table>
<tbody>
<tr>
<td class="handle">
<i class="fa fa-arrows" aria-hidden="true"></i>
</td>
<td>
{{ form_label(formdata, null, {
'label_attr': {'class': 'label-aggregation'}
@@ -22,6 +47,39 @@
{% endif %}
{% endfor %}
</ul>
<button type="submit" id="elasticsearch_settings_save" name="elasticsearch_settings[save]"
class="btn btn-primary">{{ 'Save' | trans }}</button>
<input type="hidden" id="listValues" name="facet_list_values"/>
</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

@@ -30,7 +30,11 @@
<li><p>{{ 'help::help-section-bullet: search-in-a-specific-field' | trans }}</p></li>
</ul>
</div>
<p>
<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>
<br>
<p class="text-center">{{ 'help::help-search: OR' | trans }}</p>
<br>
<p class="text-center">
<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>
</div>
{% endblock %}

View File

@@ -20,7 +20,6 @@ class StructureTest extends \PHPUnit_Framework_TestCase
$this->assertEmpty($structure->getAllFields());
$this->assertEmpty($structure->getUnrestrictedFields());
$this->assertEmpty($structure->getPrivateFields());
$this->assertEmpty($structure->getFacetFields());
$this->assertEmpty($structure->getThesaurusEnabledFields());
$this->assertEmpty($structure->getDateFields());
}
@@ -95,19 +94,6 @@ class StructureTest extends \PHPUnit_Framework_TestCase
$this->assertNotContains($unrestricted_field, $private_fields);
}
public function testGetFacetFields()
{
$facet = new Field('foo', FieldMapping::TYPE_STRING, ['facet' => Field::FACET_NO_LIMIT]);
$not_facet = new Field('bar', FieldMapping::TYPE_STRING, ['facet' => Field::FACET_DISABLED]);
$structure = new Structure();
$structure->add($facet);
$this->assertContains($facet, $structure->getFacetFields());
$structure->add($not_facet);
$facet_fields = $structure->getFacetFields();
$this->assertContains($facet, $facet_fields);
$this->assertNotContains($not_facet, $facet_fields);
}
public function testGetDateFields()
{
$string = new Field('foo', FieldMapping::TYPE_STRING);