Many bug fixes and quality enhancements to existing converters, most notably CJK pdf documents are now correctly converted to UTF-8 code.
This is one more alpha release for the upcoming stable version.
eZ Toolbox is updated to 0.5 to fix a problem with last buid of Chrome.
If you already have the extension installed it will be automatically updated.
Le billet précédent décrit les mécanismes bas niveaux d'eZ Find, et la façon dont les correspondances entre les attributs eZ Publish (noms, types de champs) et les champs Solr sont gérés. Ce billet décrit comment eZ Find peut considérablement faciliter le développement de certaines fonctionnalités (en évitant de complexes opérateurs de templates aux multiples requêtes SQL...), en ajoutant automatiquement des champs dans Solr lors de l'indexation d'un contenu, ré-exploitables par la suite pour la construction d'une facette par exemple ou pour profiter d'un filtre supplémentaire.
L'exemple étudié a surtout une valeur pédagogique, puisqu'il s'agit d'un besoin générique et relativement simple à implémenter. Les listes d'actualités, ou les listes de billets sur les Blogs proposent généralement des filtres par années (2010) ou par mois / année (Janvier 2010) (comme sur ce blog par exemple dans la colonne de droite). Habituellement, pour ce genre de filtre on développe un opérateur de template permettant d'effectuer la requête SQL nécessaire, ce qui peut rapidement devenir très complexe. Il suffit de lire le code de l'opérateur de template eZArchive pour en comprendre les limites.
Il est relativement fréquent que ces manipulations SQL souffrent de carences fonctionnelles, comme la propagation des droits, la gestion des langues, la gestion de certaines subtilités entre MySql ou PostGreSql. Ce sont des problématiques à la charge du développeur, puisque l'on contourne les API pour exploiter directement du SQL. Par ailleurs, l'opérateur eZArchive montre une autre limite importante, puisqu'il se contente de travailler sur la date de publication 'publication_date' (par facilité), et ne permet donc pas d'exploiter un attribut de date spécifique à la classe.
Les settings d'eZ Find (ezfind.ini, à surcharger dans le ezfind.ini.append.php de votre extension) permettent de déléguer le traitement de l'indexation d'un datatype eZ Publish vers une classe PHP :
[SolrFieldMapSettings] CustomMap[ezdate]=ezfSolrDocumentFieldDate
Notre classe PHP, nommée arbitrairement ezfSolrDocumentFieldDate hérite de la classe ezfSolrDocumentFieldBase et doit être ajoutée dans le dossier /extension/myextension/classes/ezfsolrdocumentfielddate.php avec le squelette suivante :
<?php class ezfSolrDocumentFieldDate extends ezfSolrDocumentFieldBase { public static function getFieldName( eZContentClassAttribute $classAttribute, $subAttribute = null, $context = 'search' ) { // return the fieldname like : attr_mydate_d } public function getData() { // return the array keys (fieldname => value), like : array('attr_mydate_dt' => '2010-04-30T00:00:00Z') } } ?>
Cette méthode est invoquée pour transformer les noms des attributs eZ Find vers les noms de champs Solr. Ainsi lorsqu'on construit une facette avec la syntaxe 'mycontentclass/mydateattribute', cette méthode reçoit 'mydateattribute' et doit retourner 'attr_mydateattribute_dt'. Nous allons donc implémenter cette fonction de la façon suivante :
const DEFAULT_SUBATTRIBUTE_TYPE = 'date'; public static function getFieldName( eZContentClassAttribute $classAttribute, $subAttribute = null, $context = 'search' ) { switch ( $classAttribute->attribute( 'data_type_string' ) ) { case 'ezdate' : { if ( $subAttribute and $subAttribute !== '' ) { // A subattribute was passed return parent::generateSubattributeFieldName( $classAttribute, $subAttribute, self::DEFAULT_SUBATTRIBUTE_TYPE ); } else { // return the default field name here. return parent::generateAttributeFieldName( $classAttribute, self::getClassAttributeType( $classAttribute, null, $context ) ); } } break; default: {} break; } }
Cette méthode est invoquée pour extraire les données d'eZ Publish, et les préparer pour leur indexation vers Solr. C'est donc dans cette méthode que l'on peut ajouter nos champs supplémentaires 'year' et 'yearmonth'. Pour faciliter la future exploitation de ces champs avec eZ Find, je souhaite pouvoir construire des facettes ou des filtres selon la syntaxe classique :
public function getData() { $contentClassAttribute = $this->ContentObjectAttribute->attribute( 'contentclass_attribute' ); switch ( $contentClassAttribute->attribute( 'data_type_string' ) ) { case 'ezdate' : { $returnArray = array(); // Get timestamp attribute value $value = $this->ContentObjectAttribute->metaData(); // Generate the main filedName attr_XXX_dt $fieldName = parent::generateAttributeFieldName( $contentClassAttribute, self::DEFAULT_ATTRIBUTE_TYPE ); $returnArray[$fieldName] = parent::convertTimestampToDate( $value ); // Generate the yearmonth subattribute filedName subattr_year_dt $fieldName = parent::generateSubattributeFieldName( $contentClassAttribute, 'year', self::DEFAULT_SUBATTRIBUTE_TYPE ); $year = date("Y", $value); // Get Year value : 2010 $returnArray[$fieldName] = parent::convertTimestampToDate( strtotime($year.'-01-01') ); // Generate the yearmonth subattribute filedName subattr_yearmonth_dt $fieldName = parent::generateSubattributeFieldName( $contentClassAttribute, 'yearmonth', self::DEFAULT_SUBATTRIBUTE_TYPE ); $month = date("n", $value); // Get Month value : 3 $returnArray[$fieldName] = parent::convertTimestampToDate( strtotime($year.'-'.$month.'-01') ); return $returnArray; } break; default: {} break; } } }
A noter : $returnArray contient un tableau à clés, dont voici un exemple de sortie (effectuer avec var_dump) :
array(3) { ["attr_date_dt"]=> string(24) "2008-12-28T00:00:00.000Z" ["subattr_date-year_dt"]=> string(24) "2008-01-01T00:00:00.000Z" ["subattr_date-yearmonth_dt"]=> string(24) "2008-12-01T00:00:00.000Z" }
A noter : Solr utilise le format de date ISO 8601, du type '2010-04-30T00:00:00Z'. La classe parent ezfSolrDocumentFieldBase propose la méthode convertTimestampToDate() pour convertir un format timestamp vers un format ISO 8601.
Nos données par années et par mois-année sont maintenant disponibles. Il ne reste plus qu'à construire nos facettes avec la syntaxe habituelle :
{def $search_yearmonth=fetch( ezfind, search, hash( query , '', 'facet', array( hash('field', 'billet/date/year', 'sort', 'alpha', 'limit', 20 ), hash('field', 'billet/date/yearmonth', 'sort', 'alpha', 'limit', 20 ) ), 'class_id', array('billet'), 'subtree_array', array(2) ))} {def $search_extras_year=$search_yearmonth['SearchExtras'].facet_fields[0].nameList|reverse} {def $search_extras_yearmonth=$search_yearmonth['SearchExtras'].facet_fields[1].nameList|reverse} {def $date_count = 0 $date_ts = 0} <li id="blog_block_10" class="colonne_block"> <h1>Archives par années :</h1> <ul class="{$current_css} list"> {foreach $search_extras_year as $facetID => $datevalue} {set $date_count = $search_yearmonth['SearchExtras'].facet_fields[0].countList[$facetID]} {set $date_ts = $datevalue|strtotime} <li><a href={concat('/Blogs/(year)/',$date_ts|datetime( 'custom', '%Y' ))|ezurl} title="Archives : {$date_ts|datetime( 'custom', '%Y' )} // {$date_count} Billet(s)">{$date_ts|datetime( 'custom', '%Y' )}</a></li> {/foreach} </ul> </li> <li id="blog_block_11" class="colonne_block"> <h1>Archives par mois / années :</h1> <ul class="{$current_css} list"> {foreach $search_extras_yearmonth as $facetID => $datevalue} {set $date_count = $search_yearmonth['SearchExtras'].facet_fields[1].countList[$facetID]} {set $date_ts = $datevalue|strtotime} <li><a href={concat('/Blogs/(year)/',$date_ts|datetime( 'custom', '%Y' ),'/(month)/',$date_ts|datetime( 'custom', '%n' ))|ezurl} title="Archives : {$date_ts|datetime( 'custom', '%F %Y' )} // {$date_count} Billet(s)">{$date_ts|datetime( 'custom', '%F %Y' )|upfirst}</a></li> {/foreach} </ul> </li> {undef $date_ts $date_count $search_yearmonth $search_extras_yearmonth}
A noter : Le fetch proposé est relativement basique, afin de faciliter la compréhension du mécanisme. Il faut bien sur faire évoluer le code pour obtenir le fonctionnel attendu (utiliser les filtres par exemple), mais ce n'est pas l'objet de ce billet puisque la documentation officielle détaille déjà la façon de procéder.
A noter : le 'sort', 'alpha' ne permet pas réellement de spécifier que l'on souhaite un tri alphabétique. Il s'agit surtout de spécifier que l'on ne souhaite pas un tri par 'count' (nombre d'items associés à la facette). Dans ce cas Solr applique un tri automatique 'croissant' en fonction de son index et du type de donnée (ce qui explique l'utilisation de l'opérateur reverse pour obtenir une liste décroissante).
The documentation of the abpdfcatalogue extension has been updated by adding a new chapter about how to customize the common or a dedicated PDF catalogue.
The documentation can be found here: http://www.alexander-block.net/eng/Business/eZ-Systems/Personal-PDF-Catalogue
Frischfilm ist nominiert für den Grimme Online Award 2010. Aus den rund 2000 Einreichungen wurden 23 herausragende Websites in die Endrunde gewählt. Am 30. Juni werden in Köln die acht Gewinner in den Kategorien Information, Wissen und Bildung, Kultur und Unterhaltung sowie Spezial bekannt gegeben. Uwe Kammann, Direktor des Grimme-Instituts, verwies auf die Vielfältigkeit und Vielseitigkeit der diesjährigen Einreichungen bei einer gleichzeitigen Ausdifferenzierung des Angebots sowie dem Gewinn an Professionalität und Reife.
Frischfilm Die Kurzfilmplattform des Schweizer Fernsehens ist ein Special-Interest-Angebot des Schweizer Fernsehens. Die User können dort unter anderem eigene Kurzfilme veröffentlichen oder auch andere Kurzfilme kommentieren und bewerten. YMC hat die Website in Zusammenarbeit mit Hinderling Volkart (Design) realisiert. Technologische Basis ist ein UGC-Framework (User Genereated Content) auf Basis von eZ Publish in einer Multimandanten-Umgebung.
Après 2 billets un peu "rapides" sur eZ Find et la gestion des datatypes, ainsi que l'utilisation des facettes pour construire un nuage de tags, voici le premier billet d'une série de tutoriels à propos d'eZ Find, qui décrivent plus en détail son fonctionnement et son utilisation avancée dans divers contextes. Cette série de tutoriels introduit quelques nouveautés de la version 2.2, sera traduite progressivement sur le share.ez.no, et servira de base pour une conférence de la eZ Conference 2010, ainsi qu'aux Recontres Mondiales du Logiciel Libre 2010
Cet article décrit comment eZ Find transforme et adapte les contenus eZ Publish, et leurs datatypes respectifs pour les indexer dans Solr. La compréhension de ces mécanismes bas niveaux d'eZ Find sont des prérequis indispensables lors des phases de développement et de debug, ne serait ce que pour savoir ou chercher et lire les portions de codes permettant de comprendre le rôle exact d'un settings, d'un paramètre ou d'un filtre.
Cet article n'a pas vocation à expliquer à nouveau comment installer et indexer son contenu avec eZ Find. La documentation décrit avec précision les opérations à effectuer. Cependant voici quelques astuces permettant de développer dans de bonnes conditions :
Il est parfois laborieux de relancer une indexation complète juste pour tester l'impact d'une modification mineure. Alors il existe pour cela quelques paramètres cachés dans le script /bin/php/updatesearchindexsolr.php, qui permettent de désigner un noeud parent de départ, ainsi q'une limite (attention les 3 paramètres combinés sont nécessaires) :
php extension/ezfind/bin/php/updatesearchindexsolr.php --siteaccess=monsiteaccess --topNodeID=2546 --offset=0 --limit=10
Pour savoir si vos contenus et vos attributs sont correctement indexés, il suffit d'effectuer une recherche dans l'interface Web d'administration de Solr : http://localhost:8983/solr/admin/. Cette interface est également utile pour vérifier comment les champs Solr ont été nommés, par exemple sur la version d'eZ Find 2.2, on peut observer 2 champs qui semblent des doublons concernent les titres des articles : attr_title_s et attr_title_t. La suite du billet détaille le comment et le pourquoi de ce comportement.
En laissant une console active, pour pourrez observer les messages envoyés vers Solr de la forme :
INFO: [] webapp=/solr path=/select params={ ... MESSAGE ... } status=400 QTime=5
Ce message peut être copier / coller à partir de la console, vers la fin de l'URL dans l'interfaçe d'administration de Solr : http://localhost:8983/solr/select/?MESSAGE. On obtient ainsi une sortie exacte de ce que Solr envois à eZ Find avant transformation. Cette manipulation est utile lors des phases de Debug, par exemple en manipulant directement le message pour obtenir exactement le résultat attendu.
Voici le parcours d'exécution du code lors de l'ajout / modification d'un contenu eZ Publish :
eZ Find est construit comme un plug-in de recherche, c'est à dire que le module /content/search a été nativement construit pour être déporté sous forme de plug-in. Ainsi dans l'extension /ezfind/settings/site.ini.append.php on trouve la déclaration du plug-in :
[SearchSettings] SearchEngine=ezsolr
C'est ensuite la méthode getEngine() (kernel/classes/ezsearch.php) qui s'occupe d'affecter la bonne classe PHP pour interfacer le plug-in de recherche, à savoir : /search/plugins/ezsolr/ezsolr.php
Lors des différentes opérations de mises à jours de contenus, la méthode addObject de la classe eZSolr sera invoqué. Ce mode de fonctionnement en plug-in permet également d'hériter des autres mécanismes natifs de la recherche eZ Publish, comme par exemple le DelayedIndexing, qui permet de déporter l'indexation (complète ou par classes) vers une tache planifiée : indexcontent.php :
[SearchSettings] DelayedIndexing=disabled|enabled|classbased DelayedIndexingClassList[] DelayedIndexingClassList[]=mycontentclass
A noter : Cette technique est relativement efficace pour optimiser les temps de réponses Back Office, ou les imports de contenus régulier. Cependant, le DelayedIndexing souffre de quelques limites, puisqu'il est générique (et non optimisé spécifiquement pour eZ Find) et qu'il se contente donc de boucler sur la table 'ezpending_actions' afin d'indexer les objets un par un sans se préoccuper des autres finesses de Solr, comme par exemple l'ajout massif de contenus à indexer, suivi d'un unique 'commit'.
La méthode addObject (/search/plugins/ezsolr/ezsolr.php) reçoit :
Cette méthode agit de la façon suivante, dans les grandes lignes :
A noter : Depuis eZ Find 2.2, il est possible d'utiliser des 'cores' spécifiques à chaque langues (/extension/ezfind/java/solr.multicore), ce qui permet de dédier des index et des fichiers de configurations spécifiques à chaque langue (spellings.txt, synonmys.txt, stopwords.txt, etc.)
Il est nécessaire de revenir sur la façon dont eZ Find nomme les champs transmis à Solr. Les principales techniques d'exploitations avancées d'eZ Find pour eZ Publish nécessitent de bien comprendre la sémantique et le mécanisme de nommage des champs.
C'est la classe /ezfind/classes/ezfsolrdocumentfieldbase.php, ou d'éventuelles classes héritées pour certains datatypes complexes (voir ma contribution ezfsolrdocumentfieldobjectrelation par exemple) qui a vocation à créer les noms des champs pour Solr, en respectant une sémantique très précise. On obtient ainsi la sémantique suivante :
Le nom des attributs :
Le nom des métadonnées :
Le nom des sous-attributs (inexploité par défaut) :
Nativement, les sous attributs sont peu ou pas exploités. C'est par contre un formidable outil pour étendre le fonctionnement d'eZ Find et indexer des champs supplémentaire. Voir par exemple ma contribution : ezfsolrdocumentfieldobjectrelation, qui indexe tous les attributs des objets en relations, en les stockant dans des sous attributs, ce qui permet ensuite d'effectuer toutes les opérations nécessaires sur ces sous attributs (recherche, filtre, facettes), selon la syntaxe 'myclass/myattribute/mysubattribute'. Les prochains billets détailleront comment exploiter ce mécanisme pour bien d'autres usages.
Dans les noms de champs Solr, la dernière information désigne le type de champs, et donc comment Solr va devoir considérer l'information (entre un string, un texte, une date, un tableau, etc.)
Coté eZ Find, cette définition se fait comme toujours dans les settings. Depuis eZ Find 2.2, il est maintenant possible de définir un type de champs spécifique à chaque contexte d'utilisation :
Par exemple, par défaut, dans la version actuelle d'eZ Find 2.2, les attributs de type 'textline' sont définis différemment pour les tris :
L'impact au niveau de Solr, c'est que tous les attributs de type 'textline' auront systématiquement 2 champs de correspondance :
A noter : il est conseillé d'utiliser également le type 'string' pour les facettes, afin de considérer le caractère espace comme un caractère lambda, et ainsi obtenir des facettes de type 'ma facette', plutôt que 'ma' et 'facette'.
Solr possède un fichier de configuration permettant à eZ Find de lui spécifier que le '_s' signifie un string, le '_t' un texte, etc. Il s'agit du fichier /ezfind/java/solr/conf/schema.xml
Ce fichier de configuration contient la définition d'un certain nombre de noms de champs en dur (pour les meta-données par exemple), mais définie également les correspondances pour tous les noms de champs dynamiques (nos fameux attributs de classes eZ Publish) :
<dynamicField name="*_i" type="int" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_f" type="float" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_d" type="double" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_si" type="sint" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_sf" type="sfloat" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_sd" type="sdouble" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_s" type="string" indexed="true" stored="true" multiValued="true" termVectors="true"/> <dynamicField name="*_sl" type="slong" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_l" type="long" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_t" type="text" indexed="true" stored="true" multiValued="true" termVectors="true"/> <dynamicField name="*_b" type="boolean" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_dt" type="date" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_random" type="random" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_k" type="keyword" indexed="true" stored="true" multiValued="true"/> <dynamicField name="*_lk" type="lckeyword" indexed="true" stored="true" multiValued="true"/> <!-- some trie-coded dynamic fields for faster range queries --> <dynamicField name="*_ti" type="tint" indexed="true" stored="true"/> <dynamicField name="*_tl" type="tlong" indexed="true" stored="true"/> <dynamicField name="*_tf" type="tfloat" indexed="true" stored="true"/> <dynamicField name="*_td" type="tdouble" indexed="true" stored="true"/> <dynamicField name="*_tdt" type="tdate" indexed="true" stored="true"/> <!-- geopoint for geospatial/location searches, boosting, ... --> <dynamicField name="*_gpt" type="geopoint" indexed="true" stored="true"/>
Ce fichier permet aussi de définir des comportements plus complexes sur certains datatypes eZ Publish, comme par exemple les mots clés (keyword). On y trouve par exemple 2 types de champs différents pour la gestion des keywords ('keyword' pour un respect de la casse et 'lckeyword' pour une casse en lowercase), qui peuvent etre exploité indifféremment au niveau d'eZ Find (DatatypeMap).
<fieldtype name="lckeyword" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <tokenizer class="solr.PatternTokenizerFactory" pattern=", *" /> <filter class="solr.TrimFilterFactory" /> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.PatternTokenizerFactory" pattern=", *" /> <filter class="solr.TrimFilterFactory" /> <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> </fieldtype>
La définition des keywords est riche d'enseignement sur la configuration de Solr. On peut observer le fonctionnement des appels des filtres Solr, et comment sont traités les séparations de mots par des virgules (PatternTokenizerFactory), la gestion de la casse (LowerCaseFilterFactory), ou encore la suppression des doublons (RemoveDuplicatesTokenFilterFactory), etc.
Enjoy!
More background information about this extension on Netgen Blog.
eZ Upgrade has been extended, supporting upgrades of eZ installations as old as 3.8.6, and upgrading them up to 4.3.0.
Now there's no excuse for not moving your 3.x site over to that shiny, new PHP 5 server. :)