diff --git a/lib/Alchemy/Phrasea/SearchEngine/AbstractConfigurationPanel.php b/lib/Alchemy/Phrasea/SearchEngine/AbstractConfigurationPanel.php index 79cc238c26..cadb88f888 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/AbstractConfigurationPanel.php +++ b/lib/Alchemy/Phrasea/SearchEngine/AbstractConfigurationPanel.php @@ -1,14 +1,31 @@ getName().'.json'; } + /** + * {@inheritdoc} + */ public function getAvailableDateFields(array $databoxes) { $date_fields = array(); diff --git a/lib/Alchemy/Phrasea/SearchEngine/ConfigurationPanelInterface.php b/lib/Alchemy/Phrasea/SearchEngine/ConfigurationPanelInterface.php index 4f2be0a709..c76ba07ef6 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/ConfigurationPanelInterface.php +++ b/lib/Alchemy/Phrasea/SearchEngine/ConfigurationPanelInterface.php @@ -1,21 +1,67 @@ searchEngine = $engine; } + /** + * {@inheritdoc} + */ public function getName() { return 'phrasea-engine'; } + /** + * {@inheritdoc} + */ public function get(Application $app, Request $request) { $configuration = $this->getConfiguration(); @@ -34,6 +49,9 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $app['twig']->render('admin/search-engine/phrasea.html.twig', $params); } + /** + * {@inheritdoc} + */ public function post(Application $app, Request $request) { $configuration = $this->getConfiguration(); @@ -50,6 +68,9 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $app->redirect($app['url_generator']->generate('admin_searchengine_get')); } + /** + * {@inheritdoc} + */ public function getConfiguration() { $configuration = @json_decode(file_get_contents($this->getConfigPathFile()), true); @@ -69,6 +90,9 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $configuration; } + /** + * {@inheritdoc} + */ public function saveConfiguration(array $configuration) { file_put_contents($this->getConfigPathFile(), json_encode($configuration)); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Phrasea/PhraseaEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Phrasea/PhraseaEngine.php index ce8f6d20a6..c5bb4addca 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Phrasea/PhraseaEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Phrasea/PhraseaEngine.php @@ -47,6 +47,9 @@ class PhraseaEngine implements SearchEngineInterface $this->options = new SearchEngineOptions(); } + /** + * {@inheritdoc} + */ public function getAvailableDateFields() { if (!$this->dateFields) { @@ -66,6 +69,9 @@ class PhraseaEngine implements SearchEngineInterface return $this->dateFields; } + /** + * {@inheritdoc} + */ public function getConfiguration() { if (!$this->configuration) { @@ -74,7 +80,10 @@ class PhraseaEngine implements SearchEngineInterface return $this->configuration; } - + + /** + * {@inheritdoc} + */ public function getDefaultSort() { $configuration = $this->getConfiguration(); @@ -82,6 +91,9 @@ class PhraseaEngine implements SearchEngineInterface return $configuration['default_sort']; } + /** + * {@inheritdoc} + */ public function getAvailableSort() { $date_fields = $this->getAvailableDateFields(); @@ -95,6 +107,9 @@ class PhraseaEngine implements SearchEngineInterface return $sort; } + /** + * {@inheritdoc} + */ public function getAvailableOrder() { return array( @@ -103,6 +118,9 @@ class PhraseaEngine implements SearchEngineInterface ); } + /** + * {@inheritdoc} + */ public function hasStemming() { return false; @@ -168,6 +186,9 @@ class PhraseaEngine implements SearchEngineInterface return $status; } + /** + * {@inheritdoc} + */ public function configurationPanel() { if (!$this->configurationPanel) { diff --git a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineInterface.php b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineInterface.php index 1f76b0ac3d..9a4ee9e5a6 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineInterface.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineInterface.php @@ -30,16 +30,34 @@ interface SearchEngineInterface */ public function status(); + /** + * @return ConfigurationPanelInterface + */ public function configurationPanel(); + /** + * @return array an array of field names + */ public function getAvailableDateFields(); + /** + * @return array an array containing criteria values as key and criteria names as value + */ public function getAvailableSort(); + /** + * @return string The default sort + */ public function getDefaultSort(); + /** + * @return array an array containing sort order values as key and sort order names as value + */ public function getAvailableOrder(); + /** + * @return Boolean return true if the search engine supports stemmed search + */ public function hasStemming(); /** @@ -120,10 +138,30 @@ interface SearchEngineInterface */ public function removeFeedEntry(\Feed_Entry_Adapter $entry); + /** + * Update an entry in the index + * + * @param \Feed_Entry_Adapter $entry + * @return SearchEngineInterface + * @throws RuntimeException + */ public function updateFeedEntry(\Feed_Entry_Adapter $entry); + /** + * Set options to search-engine + * + * @param SearchEngineOptions $options + * @return SearchEngineInterface + * @throws RuntimeException + */ public function setOptions(SearchEngineOptions $options); + /** + * Reset search-engine options + * + * @return SearchEngineInterface + * @throws RuntimeException + */ public function resetOptions(); /** diff --git a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php index 2068ad1694..d59a5cc83d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineOptions.php @@ -103,15 +103,23 @@ class SearchEngineOptions protected $business_fields = array(); /** + * Defines locale code to use for query * - * @param string $locale + * @param string $locale An i18n locale code */ public function setLocale($locale) { + if (!preg_match('/[a-z]{2,3}/', $locale)) { + throw new \InvalidArgumentException('Locale must be a valid i18n code'); + } + $this->i18n = $locale; + + return $this; } /** + * Returns the locale value * * @return string */ @@ -134,6 +142,12 @@ class SearchEngineOptions return $this; } + /** + * Allows business fields query on the given collections + * + * @param array $collection An array of collection + * @return SearchEngineOptions + */ public function allowBusinessFieldsOn(Array $collection) { $this->business_fields = $collection; @@ -141,6 +155,11 @@ class SearchEngineOptions return $this; } + /** + * Reset business fields settings + * + * @return SearchEngineOptions + */ public function disallowBusinessFields() { $this->business_fields = array(); @@ -148,12 +167,19 @@ class SearchEngineOptions return $this; } + /** + * Returns an array of collection on which business fields are allowed to + * search on + * + * @return array An array of collection + */ public function businessFieldsOn() { return $this->business_fields; } /** + * Returns the sort criteria * * @return string */ @@ -163,6 +189,7 @@ class SearchEngineOptions } /** + * Returns the sort order * * @return string */ @@ -172,6 +199,7 @@ class SearchEngineOptions } /** + * Tells whether to use stemming or not * * @param boolean $boolean * @return SearchEngineOptions @@ -184,6 +212,7 @@ class SearchEngineOptions } /** + * Return wheter the use of stemming is enabled or not * * @return boolean */ @@ -193,6 +222,7 @@ class SearchEngineOptions } /** + * Set document type to search for * * @param int $search_type * @return SearchEngineOptions @@ -213,6 +243,7 @@ class SearchEngineOptions } /** + * Returns the type of documents type to search for * * @return int */ @@ -221,6 +252,12 @@ class SearchEngineOptions return $this->search_type; } + /** + * Set the collections where to search for + * + * @param array $collections An array of collection + * @return SearchEngineOptions + */ public function onCollections(Array $collections) { $this->collections = $collections; @@ -229,14 +266,21 @@ class SearchEngineOptions } /** + * Returns the collections on which the search occurs * - * @return array + * @return array An array of collection */ public function collections() { return $this->collections; } + /** + * Returns an array containing all the databoxes where the search will + * happen + * + * @return array + */ public function databoxes() { $databoxes = array(); @@ -429,13 +473,13 @@ class SearchEngineOptions } if (in_array($key, array('date_fields', 'fields'))) { $value = array_map(function(\databox_field $field) { - return $field->get_databox()->get_sbas_id() . '_' . $field->get_id(); - }, $value); + return $field->get_databox()->get_sbas_id() . '_' . $field->get_id(); + }, $value); } if (in_array($key, array('collections', 'business_fields'))) { $value = array_map(function($collection) { - return $collection->get_base_id(); - }, $value); + return $collection->get_base_id(); + }, $value); } $ret[$key] = $value; @@ -480,16 +524,16 @@ class SearchEngineOptions break; case in_array($key, array('date_fields', 'fields')): $value = array_map(function($serialized) use ($app) { - $data = explode('_', $serialized); + $data = explode('_', $serialized); - return \databox_field::get_instance($app, $app['phraseanet.appbox']->get_databox($data[0]), $data[1]); - return \collection::get_from_base_id($app, $base_id); - }, $value); + return \databox_field::get_instance($app, $app['phraseanet.appbox']->get_databox($data[0]), $data[1]); + return \collection::get_from_base_id($app, $base_id); + }, $value); break; case in_array($key, array('collections', 'business_fields')): $value = array_map(function($base_id) use ($app) { - return \collection::get_from_base_id($app, $base_id); - }, $value); + return \collection::get_from_base_id($app, $base_id); + }, $value); break; } @@ -512,7 +556,9 @@ class SearchEngineOptions $options->setMaxDate($value); break; case 'i18n': - $options->setLocale($value); + if ($value) { + $options->setLocale($value); + } break; case 'stemming': $options->useStemming($value); @@ -539,7 +585,6 @@ class SearchEngineOptions throw new \RuntimeException(sprintf('Unable to handle key `%s`', $key)); break; } - } if ($sort_by) { @@ -552,4 +597,5 @@ class SearchEngineOptions return $options; } + } diff --git a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineResult.php b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineResult.php index dbd16bffe7..7d47230ef3 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineResult.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineResult.php @@ -33,8 +33,8 @@ class SearchEngineResult $this->query = $query; $this->duration = (float) $duration; $this->offsetStart = (int) $offsetStart; - $this->available = (int)$available; - $this->total = (int)$total; + $this->available = (int) $available; + $this->total = (int) $total; $this->error = $error; $this->warning = $warning; $this->suggestions = $suggestions; @@ -44,62 +44,130 @@ class SearchEngineResult return $this; } + /** + * An collection of results + * + * @return ArrayCollection + */ public function results() { return $this->results; } - + /** + * The query related to these results + * + * @return string + */ public function query() { return $this->query; } + /** + * The duration of the query + * + * @return float + */ public function duration() { return $this->duration; } + /** + * Return the number of page depending on the amount displayed on each page + * + * @param integer $amountPerPage + * @return integer + */ public function totalPages($amountPerPage) { return ceil($this->available / $amountPerPage); } + /** + * Return the number of the current page depending on the amount displayed + * on each page + * + * @param integer $amountPerPage + * @return integer + */ public function currentPage($amountPerPage) { return ceil($this->offsetStart / $amountPerPage); } + /** + * Return the number of results that can be returned by the search engine + * + * The difference with 'total' is that this method return the actual number + * of results that can be fetched whereas 'total' returns the number of + * results that matches the query (can be greater than available quantity) + * + * @return int + */ public function available() { return $this->available; } + /** + * Return the number of items that match the query. Some items may be not + * retrievable. To get the number of results that can be retrieved, use + * the 'available' method + * + * @return int + */ public function total() { return $this->total; } + /** + * Return an error message returned by the search engine + * + * @return string + */ public function error() { return $this->error; } + /** + * Return a warning message returned by the search engine + * + * @return string + */ public function warning() { return $this->warning; } + /** + * Return a collection of SearchEngineSuggestion + * + * @return ArrayCollection + */ public function suggestions() { return $this->suggestions; } + /** + * Return HTML proposals + * + * @return string + */ public function proposals() { return $this->propositions; } + /** + * Return the index name where the query happened + * + * @return string + */ public function indexes() { return $this->indexes; diff --git a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineSuggestion.php b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineSuggestion.php index 3d6e282a1d..16b24ef12d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SearchEngineSuggestion.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SearchEngineSuggestion.php @@ -13,8 +13,20 @@ namespace Alchemy\Phrasea\SearchEngine; class SearchEngineSuggestion { + + /** + * @var string + */ private $query; + + /** + * @var string + */ private $suggestion; + + /** + * @var int + */ private $hits; public function __construct($query, $suggestion, $hits) @@ -24,18 +36,34 @@ class SearchEngineSuggestion $this->hits = (int) $hits; } + /** + * The query related to the suggestion + * + * @return string + */ public function query() { return $this->query; } + /** + * The actual suggestion + * + * @return string + */ public function suggestion() { return $this->suggestion; } + /** + * The number of hits + * + * @return int + */ public function hits() { return $this->hits; } + } diff --git a/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/AbstractCharset.php b/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/AbstractCharset.php index d453f8e005..87c88a2877 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/AbstractCharset.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/AbstractCharset.php @@ -11,11 +11,6 @@ namespace Alchemy\Phrasea\SearchEngine\SphinxSearch; -/** - * - * @license http://opensource.org/licenses/gpl-3.0 GPLv3 - * @link www.phraseanet.com - */ abstract class AbstractCharset { protected $table; diff --git a/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/ConfigurationPanel.php b/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/ConfigurationPanel.php index 9bbccd77b8..3c761dcb4d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/ConfigurationPanel.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/ConfigurationPanel.php @@ -1,5 +1,14 @@ searchEngine = $engine; } + /** + * {@inheritdoc} + */ public function getName() { return 'sphinx-search'; } + /** + * {@inheritdoc} + */ public function get(Application $app, Request $request) { $configuration = $this->getConfiguration(); @@ -38,6 +53,9 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $app['twig']->render('admin/search-engine/sphinx-search.html.twig', $params); } + /** + * {@inheritdoc} + */ public function post(Application $app, Request $request) { $configuration = $this->getConfiguration(); @@ -61,6 +79,9 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $app->redirect($app['url_generator']->generate('admin_searchengine_get')); } + /** + * {@inheritdoc} + */ public function getConfiguration() { $configuration = @json_decode(file_get_contents($this->getConfigPathFile()), true); @@ -96,6 +117,9 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $configuration; } + /** + * {@inheritdoc} + */ public function saveConfiguration(array $configuration) { file_put_contents($this->getConfigPathFile(), json_encode($configuration)); @@ -103,6 +127,11 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $this; } + /** + * Returns all the charset Sphinx Search supports + * + * @return array An array of charsets + */ public function getAvailableCharsets() { if (null !== $this->charsets) { @@ -127,6 +156,13 @@ class ConfigurationPanel extends AbstractConfigurationPanel return $this->charsets; } + /** + * Generates Sphinx Search configuration depending on the service configuration + * + * @param array $databoxes The databoxes to index + * @param array $configuration The configuration + * @return string The sphinx search configuration + */ public function generateSphinxConf(array $databoxes, array $configuration) { $defaults = array( diff --git a/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/SphinxSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/SphinxSearchEngine.php index 8bc20329b8..043f26f868 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/SphinxSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/SphinxSearch/SphinxSearchEngine.php @@ -71,6 +71,9 @@ class SphinxSearchEngine implements SearchEngineInterface return $this; } + /** + * {@inheritdoc} + */ public function getAvailableDateFields() { if (!$this->dateFields) { @@ -89,12 +92,18 @@ class SphinxSearchEngine implements SearchEngineInterface return $this->dateFields; } - + + /** + * {@inheritdoc} + */ public function getDefaultSort() { return 'relevance'; } + /** + * {@inheritdoc} + */ public function getAvailableSort() { return array( @@ -104,6 +113,9 @@ class SphinxSearchEngine implements SearchEngineInterface ); } + /** + * {@inheritdoc} + */ public function getAvailableOrder() { return array( @@ -111,12 +123,18 @@ class SphinxSearchEngine implements SearchEngineInterface 'asc' => _('ascendant'), ); } - + + /** + * {@inheritdoc} + */ public function hasStemming() { return true; } + /** + * {@inheritdoc} + */ public function status() { if (false === $this->sphinx->Status()) { @@ -135,8 +153,7 @@ class SphinxSearchEngine implements SearchEngineInterface } /** - * - * @return ConfigurationPanel + * {@inheritdoc} */ public function configurationPanel() { @@ -156,11 +173,17 @@ class SphinxSearchEngine implements SearchEngineInterface return $this->configuration; } + /** + * {@inheritdoc} + */ public function availableTypes() { return array(self::GEM_TYPE_RECORD, self::GEM_TYPE_STORY); } + /** + * {@inheritdoc} + */ public function addRecord(\record_adapter $record) { if (!$this->rt_conn) { @@ -235,31 +258,9 @@ class SphinxSearchEngine implements SearchEngineInterface return $this; } - private function getSqlDateFields(\record_adapter $record) - { - $configuration = $this->getConfiguration(); - - $sql_fields = array(); - - foreach ($configuration['date_fields'] as $field_name) { - - try { - $value = $record->get_caption()->get_field($field_name)->get_serialized_values(); - } catch (\Exception $e) { - $value = null; - } - - if ($value) { - $date = \DateTime::createFromFormat('Y/m/d H:i:s', $this->app['unicode']->parseDate($value)); - $value = $date->format('U'); - } - - $sql_fields[] = $value ? : '-1'; - } - - return ($sql_fields ? ', ' : '') . implode(',', $sql_fields); - } - + /** + * {@inheritdoc} + */ public function removeRecord(\record_adapter $record) { if (!$this->rt_conn) { @@ -300,48 +301,75 @@ class SphinxSearchEngine implements SearchEngineInterface return $this; } + /** + * {@inheritdoc} + */ public function updateRecord(\record_adapter $record) { $this->removeRecord($record); $this->addRecord($record); } + /** + * {@inheritdoc} + */ public function addStory(\record_adapter $record) { return $this->addRecord($record); } + /** + * {@inheritdoc} + */ public function removeStory(\record_adapter $record) { return $this->removeRecord($record); } + /** + * {@inheritdoc} + */ public function updateStory(\record_adapter $record) { return $this->updateRecord($record); } + /** + * {@inheritdoc} + */ public function addFeedEntry(\Feed_Entry_Adapter $entry) { throw new RuntimeException('Feed Entry indexing not supported by Sphinx Search Engine'); } + /** + * {@inheritdoc} + */ public function removeFeedEntry(\Feed_Entry_Adapter $entry) { throw new RuntimeException('Feed Entry indexing not supported by Sphinx Search Engine'); } + /** + * {@inheritdoc} + */ public function updateFeedEntry(\Feed_Entry_Adapter $entry) { throw new RuntimeException('Feed Entry indexing not supported by Sphinx Search Engine'); } + /** + * {@inheritdoc} + */ public function setOptions(SearchEngineOptions $options) { $this->options = $options; $this->applyOptions($options); } + /** + * {@inheritdoc} + */ public function resetOptions() { $this->options = new SearchEngineOptions(); @@ -354,6 +382,9 @@ class SphinxSearchEngine implements SearchEngineInterface $this->sphinx->ResetFilters(); } + /** + * {@inheritdoc} + */ public function query($query, $offset, $perPage) { assert(is_int($offset)); @@ -423,6 +454,9 @@ class SphinxSearchEngine implements SearchEngineInterface return new SearchEngineResult($results, $query, $duration, $offset, $available, $total, $error, $warning, $suggestions, $propositions, $index); } + /** + * {@inheritdoc} + */ public function autocomplete($query) { $words = explode(" ", $this->cleanupQuery($query)); @@ -430,6 +464,9 @@ class SphinxSearchEngine implements SearchEngineInterface return $this->getSuggestions(array_pop($words)); } + /** + * {@inheritdoc} + */ public function excerpt($query, $fields, \record_adapter $record) { $index = ''; @@ -462,6 +499,9 @@ class SphinxSearchEngine implements SearchEngineInterface return $this->sphinx->BuildExcerpts($fields_to_send, $index, $query, $opts); } + /** + * {@inheritdoc} + */ public function resetCache() { return $this; @@ -608,6 +648,31 @@ class SphinxSearchEngine implements SearchEngineInterface return $this; } + private function getSqlDateFields(\record_adapter $record) + { + $configuration = $this->getConfiguration(); + + $sql_fields = array(); + + foreach ($configuration['date_fields'] as $field_name) { + + try { + $value = $record->get_caption()->get_field($field_name)->get_serialized_values(); + } catch (\Exception $e) { + $value = null; + } + + if ($value) { + $date = \DateTime::createFromFormat('Y/m/d H:i:s', $this->app['unicode']->parseDate($value)); + $value = $date->format('U'); + } + + $sql_fields[] = $value ? : '-1'; + } + + return ($sql_fields ? ', ' : '') . implode(',', $sql_fields); + } + /** * Remove all keywords, operators, quotes from a query string *