diff --git a/docker/phraseanet/auto-install.sh b/docker/phraseanet/auto-install.sh index 4028e5a614..b69490fd03 100755 --- a/docker/phraseanet/auto-install.sh +++ b/docker/phraseanet/auto-install.sh @@ -2,6 +2,16 @@ set -xe +if [ $INSTALL_ACCOUNT_EMAIL = ""]; then + echo "INSTALL_ACCOUNT_EMAIL var is not set." + exit 1 +fi + +if [ $INSTALL_ACCOUNT_PASSWORD = ""]; then + echo "INSTALL_ACCOUNT_PASSWORD var is not set." + exit 1 +fi + /var/alchemy/Phraseanet/bin/setup system:install \ --email=$INSTALL_ACCOUNT_EMAIL \ --password=$INSTALL_ACCOUNT_PASSWORD \ diff --git a/lib/Alchemy/Phrasea/Cache/RedisCache.php b/lib/Alchemy/Phrasea/Cache/RedisCache.php index 1130112563..365461256f 100644 --- a/lib/Alchemy/Phrasea/Cache/RedisCache.php +++ b/lib/Alchemy/Phrasea/Cache/RedisCache.php @@ -73,7 +73,7 @@ class RedisCache extends CacheProvider implements Cache */ protected function doDelete($id) { - return $this->_redis->delete($id); + return $this->_redis->del($id); } /** diff --git a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php index 790e33525d..a6706424aa 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/QueryController.php @@ -382,7 +382,6 @@ class QueryController extends Controller 'labels' => $field->get_labels(), 'type' => $field->get_type(), 'field' => $field->get_name(), - 'query' => "field." . $field->get_name() . ":%s", 'trans_label' => $field->get_label($this->app['locale']), ]; $field->get_label($this->app['locale']); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php index 4bc98ec6b9..bf239ffbd1 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php @@ -45,6 +45,9 @@ class TextNode extends AbstractTermNode implements ContextAbleInterface foreach ($context->localizeField($field) as $f) { $index_fields[] = $f; } + foreach ($context->truncationField($field) as $f) { + $index_fields[] = $f; + } } if (!$index_fields) { return null; diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php index 87d54c5b71..0532f85c30 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticsearchOptions.php @@ -323,7 +323,7 @@ class ElasticsearchOptions "aggregated (2 values: fired = 0 or 1)" => -1, ], 'output_formatter' => function($value) { - static $map = ['0'=>"No flash", '1'=>"Flash"]; + static $map = ["false"=>"No flash", "true"=>"Flash", '0'=>"No flash", '1'=>"Flash"]; return array_key_exists($value, $map) ? $map[$value] : $value; }, ], diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Index.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Index.php index dd5d942f78..9671b83226 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Index.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Index.php @@ -90,6 +90,16 @@ class Index // TODO Maybe replace nfkc_normalizer + asciifolding with icu_folding 'filter' => ['nfkc_normalizer', 'asciifolding'] ], + 'truncation_analyzer' => [ + 'type' => 'custom', + 'tokenizer' => 'truncation_tokenizer', + 'filter' => ['lowercase', 'stop', 'kstem'] + ], + 'truncation_analyzer#search' => [ + 'type' => 'custom', + 'tokenizer' => 'truncation_tokenizer', + 'filter' => ['lowercase', 'stop', 'kstem'] + ], // Lang specific 'fr_full' => [ 'type' => 'custom', @@ -145,6 +155,12 @@ class Index ] ], 'tokenizer' => [ + 'truncation_tokenizer' => [ + "type" => "edgeNGram", + "min_gram" => "2", + "max_gram" => "15", + "token_chars" => [ "letter", "digit", "punctuation", "symbol" ] + ], 'thesaurus_path' => [ 'type' => 'path_hierarchy' ] diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping/StringFieldMapping.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping/StringFieldMapping.php index b9022b2238..4f7c9d360d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping/StringFieldMapping.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Mapping/StringFieldMapping.php @@ -55,8 +55,13 @@ class StringFieldMapping extends ComplexFieldMapping { $child = new StringFieldMapping('light'); $child->setAnalyzer('general_light'); - $this->addChild($child); + + $child = new StringFieldMapping('truncated'); + $child->setAnalyzer('truncation_analyzer', 'indexing'); + $child->setAnalyzer('truncation_analyzer#search', 'searching'); + $this->addChild($child); + $this->addLocalizedChildren($locales); return $this; diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/Escaper.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/Escaper.php index 728ed3e372..6369d426cc 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/Escaper.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/Escaper.php @@ -4,6 +4,11 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; class Escaper { + public function quoteWord($value) + { + return '"' . $this->escapeRaw($value) . '"'; + } + public function escapeWord($value) { // Strip double quotes from values to prevent broken queries diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/FacetsResponse.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/FacetsResponse.php index 42c5a63e25..2f2d966c51 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/FacetsResponse.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/FacetsResponse.php @@ -62,7 +62,7 @@ class FacetsResponse 'value' => $key, 'raw_value' => $key, 'count' => $bucket['doc_count'], - 'query' => sprintf('field.%s:%s', $this->escaper->escapeWord($name), $this->escaper->escapeWord($key)) + 'query' => sprintf('field.%s=%s', $this->escaper->escapeWord($name), $this->escaper->quoteWord($key)) ]; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php index cd65ed8d1d..69baf9f5bf 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php @@ -9,6 +9,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field; use Alchemy\Phrasea\SearchEngine\Elastic\AST\Field as ASTField; use Alchemy\Phrasea\SearchEngine\Elastic\AST\Flag; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; +use Alchemy\Phrasea\SearchEngine\SearchEngineOptions; /** * @todo Check for private fields and only search on them if allowed @@ -23,13 +24,23 @@ class QueryContext private $queryLocale; /** @var array */ private $fields; + /** @var SearchEngineOptions */ + private $options; - public function __construct(Structure $structure, array $locales, $queryLocale, array $fields = null) + /** + * @param SearchEngineOptions|null $options + * @param Structure $structure + * @param array $locales + * @param $queryLocale + * @param array $fields + */ + public function __construct($options, Structure $structure, array $locales, $queryLocale, array $fields = null) { $this->structure = $structure; $this->locales = $locales; $this->queryLocale = $queryLocale; $this->fields = $fields; + $this->options = $options; } public function narrowToFields(array $fields) @@ -43,7 +54,7 @@ class QueryContext } } - return new static($this->structure, $this->locales, $this->queryLocale, $fields); + return new static($this->options, $this->structure, $this->locales, $this->queryLocale, $fields); } /** @@ -131,6 +142,16 @@ class QueryContext return $ret; } + public function truncationField(Field $field) + { + if($this->options && $this->options->useTruncation()) { + return [sprintf('%s.truncated', $field->getIndexField())]; + } + else { + return []; + } + } + private function localizeFieldName($field) { $fields = array(); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php index f199434677..9443c79655 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContextFactory.php @@ -23,7 +23,7 @@ class QueryContextFactory ? $this->getLimitedStructure($options) : $this->structure; - $context = new QueryContext($structure, $this->locales, $this->current_locale); + $context = new QueryContext($options, $structure, $this->locales, $this->current_locale); if ($options) { $fields = $this->getSearchedFields($options); diff --git a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php index f52fbcdf21..5e942b1adf 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php @@ -71,6 +71,8 @@ class SearchEngineOptions protected $i18n; /** @var bool */ protected $stemming = true; + /** @var bool */ + protected $use_truncation = false; /** @var string */ protected $sort_by; @@ -105,7 +107,8 @@ class SearchEngineOptions 'sort_ord', 'business_fields', 'max_results', - 'first_result' + 'first_result', + 'use_truncation', ]; /** @@ -217,6 +220,29 @@ class SearchEngineOptions return $this; } + /** + * Tells whether to use truncation or not + * + * @param boolean $boolean + * @return $this + */ + public function setUseTruncation($boolean) + { + $this->use_truncation = !!$boolean; + + return $this; + } + + /** + * Return wheter the use of truncation is enabled or not + * + * @return boolean + */ + public function useTruncation() + { + return $this->use_truncation; + } + /** * Return wheter the use of stemming is enabled or not * @@ -542,6 +568,8 @@ class SearchEngineOptions $options->setFields($databoxFields); $options->setDateFields($databoxDateFields); + $options->setUseTruncation((Boolean) $request->get('truncation')); + return $options; } @@ -628,6 +656,7 @@ class SearchEngineOptions } }, 'stemming' => $optionSetter('setStemming'), + 'use_truncation' => $optionSetter('setUseTruncation'), 'date_fields' => function ($value, SearchEngineOptions $options) use ($fieldNormalizer) { $options->setDateFields($fieldNormalizer($value)); }, diff --git a/lib/Alchemy/Phrasea/Utilities/RedisSessionHandler.php b/lib/Alchemy/Phrasea/Utilities/RedisSessionHandler.php index b2e8c008cc..fd5ee8470d 100644 --- a/lib/Alchemy/Phrasea/Utilities/RedisSessionHandler.php +++ b/lib/Alchemy/Phrasea/Utilities/RedisSessionHandler.php @@ -80,7 +80,7 @@ class RedisSessionHandler implements \SessionHandlerInterface */ public function destroy($sessionId) { - return 1 === $this->redis->delete($this->prefix.$sessionId); + return 1 === $this->redis->del($this->prefix.$sessionId); } /** diff --git a/resources/www/common/styles/main.scss b/resources/www/common/styles/main.scss index 805e018608..abcf466262 100644 --- a/resources/www/common/styles/main.scss +++ b/resources/www/common/styles/main.scss @@ -50,7 +50,9 @@ $mainMenuLinkBackgroundHoverColor: transparent; } } - +[class*=" icon-"].fa, [class^=icon-].fa, .fa { + font-family: Fontawesome!important; +} [class^="icon-"], [class*=" icon-"].icomoon { display: inline-block; width: inherit; diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php b/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php index b2cde860b1..571cbd93de 100644 --- a/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/AST/TextNodeTest.php @@ -48,6 +48,7 @@ class TextNodeTest extends \PHPUnit_Framework_TestCase $query_context->getUnrestrictedFields()->willReturn([$field]); $query_context->getPrivateFields()->willReturn([]); $query_context->localizeField($field)->willReturn(['foo.fr', 'foo.en']); + $query_context->truncationField($field)->willReturn([]); $node = new TextNode('bar', new Context('baz')); $query = $node->buildQuery($query_context->reveal()); @@ -80,12 +81,18 @@ class TextNodeTest extends \PHPUnit_Framework_TestCase $query_context ->localizeField($public_field) ->willReturn(['foo.fr', 'foo.en']); + $query_context + ->truncationField($public_field) + ->willReturn([]); $query_context ->getPrivateFields() ->willReturn([$private_field]); $query_context ->localizeField($private_field) ->willReturn(['private_caption.bar.fr', 'private_caption.bar.en']); + $query_context + ->truncationField($private_field) + ->willReturn([]); $node = new TextNode('baz'); $query = $node->buildQuery($query_context->reveal()); @@ -136,6 +143,7 @@ class TextNodeTest extends \PHPUnit_Framework_TestCase $query_context->getUnrestrictedFields()->willReturn([$field]); $query_context->getPrivateFields()->willReturn([]); $query_context->localizeField($field)->willReturn(['foo.fr', 'foo.en']); + $query_context->truncationField($field)->willReturn([]); $node = new TextNode('bar'); $node->setConcepts([ @@ -180,12 +188,18 @@ class TextNodeTest extends \PHPUnit_Framework_TestCase $query_context ->localizeField($public_field) ->willReturn(['foo.fr', 'foo.en']); + $query_context + ->truncationField($public_field) + ->willReturn([]); $query_context ->getPrivateFields() ->willReturn([$private_field]); $query_context ->localizeField($private_field) ->willReturn(['private_caption.bar.fr', 'private_caption.bar.en']); + $query_context + ->truncationField($private_field) + ->willReturn([]); $node = new TextNode('baz'); $node->setConcepts([ diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/Search/QueryContextTest.php b/tests/Alchemy/Tests/Phrasea/SearchEngine/Search/QueryContextTest.php index e45a69b87b..093ee51f3a 100644 --- a/tests/Alchemy/Tests/Phrasea/SearchEngine/Search/QueryContextTest.php +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/Search/QueryContextTest.php @@ -18,7 +18,7 @@ class QueryContextTest extends \PHPUnit_Framework_TestCase { $structure = $this->prophesize(Structure::class)->reveal(); $available_locales = ['ab', 'cd', 'ef']; - $context = new QueryContext($structure, $available_locales, 'fr'); + $context = new QueryContext(null, $structure, $available_locales, 'fr'); $narrowed = $context->narrowToFields(['some_field']); $this->assertEquals(['some_field'], $narrowed->getFields()); } @@ -33,10 +33,10 @@ class QueryContextTest extends \PHPUnit_Framework_TestCase 'bar' => $bar_field ]); - $context = new QueryContext($structure->reveal(), [], 'fr'); + $context = new QueryContext(null, $structure->reveal(), [], 'fr'); $this->assertEquals([$foo_field, $bar_field], $context->getUnrestrictedFields()); - $narrowed_context = new QueryContext($structure->reveal(), [], 'fr', ['foo']); + $narrowed_context = new QueryContext(null, $structure->reveal(), [], 'fr', ['foo']); $this->assertEquals([$foo_field], $narrowed_context->getUnrestrictedFields()); } @@ -50,10 +50,10 @@ class QueryContextTest extends \PHPUnit_Framework_TestCase 'bar' => $bar_field ]); - $context = new QueryContext($structure->reveal(), [], 'fr'); + $context = new QueryContext(null, $structure->reveal(), [], 'fr'); $this->assertEquals([$foo_field, $bar_field], $context->getPrivateFields()); - $narrowed_context = new QueryContext($structure->reveal(), [], 'fr', ['foo']); + $narrowed_context = new QueryContext(null, $structure->reveal(), [], 'fr', ['foo']); $this->assertEquals([$foo_field], $narrowed_context->getPrivateFields()); } } diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/Structure/ValueCheckerTest.php b/tests/Alchemy/Tests/Phrasea/SearchEngine/Structure/ValueCheckerTest.php index fe4fd0c7d4..b0e2b6aff8 100644 --- a/tests/Alchemy/Tests/Phrasea/SearchEngine/Structure/ValueCheckerTest.php +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/Structure/ValueCheckerTest.php @@ -14,14 +14,14 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Typed; class ValueCheckerTest extends \PHPUnit_Framework_TestCase { /** - * @dataProvider escapeRawProvider + * @dataProvider valueCheckerProvider */ public function testValueCompatibility($subject, $value, $compatible) { $this->assertEquals($compatible, ValueChecker::isValueCompatible($subject, $value)); } - public function escapeRawProvider() + public function valueCheckerProvider() { $values = [ [FieldMapping::TYPE_FLOAT , 42 , true ],