Author: mj
Date: Tue Feb  7 13:38:57 2012
New Revision: 10815

Log:
Snapshot commit against issue 2199.  Refactor mapping into object-specific 
classes, add bulk load functionality, refactor results template.  Stubs for 
document mapping and facet retreival (in progress).

Added:
   trunk/plugins/qtElasticSearchPlugin/lib/model/
   trunk/plugins/qtElasticSearchPlugin/lib/model/QubitActorMapping.class.php
   
trunk/plugins/qtElasticSearchPlugin/lib/model/QubitInformationObjectMapping.class.php
Modified:
   trunk/lib/QubitSearch.class.php
   
trunk/plugins/qtElasticSearchPlugin/config/qtElasticSearchPluginConfiguration.class.php
   trunk/plugins/qtElasticSearchPlugin/lib/qtElasticSearchPlugin.class.php
   
trunk/plugins/qtElasticSearchPlugin/modules/search/actions/indexAction.class.php
   
trunk/plugins/qtElasticSearchPlugin/modules/search/templates/_searchResults.php

Modified: trunk/lib/QubitSearch.class.php
==============================================================================
--- trunk/lib/QubitSearch.class.php     Tue Feb  7 00:27:13 2012        (r10814)
+++ trunk/lib/QubitSearch.class.php     Tue Feb  7 13:38:57 2012        (r10815)
@@ -19,7 +19,6 @@
 
 class QubitSearch extends xfIndexSingle
 {
-
   // allow disabling search index via boolean flag
   public $disabled = false;
 
@@ -75,6 +74,11 @@
    */
   protected function initialize()
   {
+    if (self::$_instance instanceof qtElasticSearchPlugin)
+    {
+      return;
+    }
+
     $this->setEngine(new 
xfLuceneEngine(sfConfig::get('sf_data_dir').'/index'));
     $this->getEngine()->open();
     
Zend_Search_Lucene_Search_QueryParser::setDefaultOperator(Zend_Search_Lucene_Search_QueryParser::B_AND);
@@ -87,6 +91,7 @@
   {
     if (self::getInstance() instanceof qtElasticSearchPlugin)
     {
+      self::getInstance()->logger = $this->getLogger();
       self::getInstance()->qubitPopulate($options);
       return;
     }
@@ -199,6 +204,11 @@
 
   public function optimize()
   {
+    if (self::getInstance() instanceof qtElasticSearchPlugin)
+    {
+      return;
+    }
+
     $start = microtime(true);
     $this->getLogger()->log('Optimizing index...', $this->getName());
     $this->getEngine()->optimize();

Modified: 
trunk/plugins/qtElasticSearchPlugin/config/qtElasticSearchPluginConfiguration.class.php
==============================================================================
--- 
trunk/plugins/qtElasticSearchPlugin/config/qtElasticSearchPluginConfiguration.class.php
     Tue Feb  7 00:27:13 2012        (r10814)
+++ 
trunk/plugins/qtElasticSearchPlugin/config/qtElasticSearchPluginConfiguration.class.php
     Tue Feb  7 13:38:57 2012        (r10815)
@@ -26,6 +26,9 @@
     // this is the name of the index; must be unique within the server
     $index = 'qubit',
 
+    // default number of documents to include in a batch (bulk) request
+    $batchSize = 500,
+
     // server defaults to localhost:9200 if omitted
     // can also be used to configure a cluster of ElasticSearch nodes
     // see more info at: http://ruflin.github.com/Elastica/

Added: trunk/plugins/qtElasticSearchPlugin/lib/model/QubitActorMapping.class.php
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ trunk/plugins/qtElasticSearchPlugin/lib/model/QubitActorMapping.class.php   
Tue Feb  7 13:38:57 2012        (r10815)
@@ -0,0 +1,90 @@
+<?php
+  /*
+  * This file is part of Qubit Toolkit.
+  *
+  * Qubit Toolkit is free software: you can redistribute it and/or modify
+  * it under the terms of the GNU Affero General Public License as published by
+  * the Free Software Foundation, either version 3 of the License, or
+  * (at your option) any later version.
+  *
+  * Qubit Toolkit is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  * GNU General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License
+  * along with Qubit Toolkit.  If not, see <http://www.gnu.org/licenses/>.
+  */
+
+  /**
+   * This class is used to provide a model mapping for storing QubitActor 
objects
+   * within an ElasticSearch document index.
+   *
+   * @package    qtElasticSearchPlugin
+   * @author     MJ Suhonos <[email protected]>
+   * @version    svn:$Id: QubitActorMapping.class.php 10316 2011-11-14 
22:40:18Z mj $
+   */
+
+  class QubitActorMapping
+  {
+    static function getProperties()
+    {
+      // TODO ...
+      return array();
+    }
+
+    static function serialize($object)
+    {
+      $serialized = array();
+
+      $objectI18ns = $object->actorI18ns->indexBy('culture');
+
+      // Add other forms of name
+      $criteria = new Criteria;
+      $criteria->addJoin(QubitOtherNameI18n::ID, QubitOtherName::ID);
+      $criteria->add(QubitOtherName::OBJECT_ID, $object->id);
+
+      $otherNames = QubitOtherNameI18n::get($criteria);
+
+      foreach ($otherNames as $otherName)
+      {
+        $serialized[$otherName->culture]['otherNames'][] = $otherName->name;
+      }
+
+      // get all i18n-ized versions of this object
+      foreach ($objectI18ns as $culture => $objectI18n)
+      {
+        // index all values on the i18n-ized object
+        foreach (self::getI18nFields(get_class($object)) as $method)
+        {
+          $value = call_user_func(array($objectI18n, 'get' . $method));
+          if (!is_null($value))
+          {
+            $serialized[$culture][lcfirst($method)] = $value;
+          }
+        }
+      }
+
+      return $serialized;
+    }
+
+    public static function getI18nFields($class)
+    {
+      // use reflection on i18n object to get property list from class 
constants
+      if (class_exists($class . 'I18n'))
+      {
+        $reflect = new ReflectionClass($class . 'I18n');
+        $i18nFields = $reflect->getConstants();
+
+        // these constants cannot be accessed by __get()
+        unset($i18nFields['DATABASE_NAME']);
+        unset($i18nFields['TABLE_NAME']);
+
+        // id and culture are not used for indexing
+        unset($i18nFields['ID']);
+        unset($i18nFields['CULTURE']);
+
+        return array_map(array('sfInflector', 'camelize'), 
array_map('strtolower', array_keys($i18nFields)));
+      }
+    }
+  }
\ No newline at end of file

Added: 
trunk/plugins/qtElasticSearchPlugin/lib/model/QubitInformationObjectMapping.class.php
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ 
trunk/plugins/qtElasticSearchPlugin/lib/model/QubitInformationObjectMapping.class.php
       Tue Feb  7 13:38:57 2012        (r10815)
@@ -0,0 +1,185 @@
+<?php
+  /*
+  * This file is part of Qubit Toolkit.
+  *
+  * Qubit Toolkit is free software: you can redistribute it and/or modify
+  * it under the terms of the GNU Affero General Public License as published by
+  * the Free Software Foundation, either version 3 of the License, or
+  * (at your option) any later version.
+  *
+  * Qubit Toolkit is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  * GNU General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License
+  * along with Qubit Toolkit.  If not, see <http://www.gnu.org/licenses/>.
+  */
+
+  /**
+   * This class is used to provide a model mapping for storing 
QubitInformationObject objects
+   * within an ElasticSearch document index.
+   *
+   * @package    qtElasticSearchPlugin
+   * @author     MJ Suhonos <[email protected]>
+   * @version    svn:$Id: QubitInformationObjectMapping.class.php 10316 
2011-11-14 22:40:18Z mj $
+   */
+
+  class QubitInformationObjectMapping
+  {
+    static function getProperties()
+    {
+      // TODO ...
+      return array();
+    }
+
+    static function serialize($object)
+    {
+      $serialized = array();
+
+      $objectI18ns = $object->informationObjectI18ns->indexBy('culture');
+
+      // indexed?
+      $serialized['slug'] = $object->slug;
+      $serialized['referenceCode'] = $object->referenceCode;
+      $serialized['identifier'] = $object->identifier;
+
+      // used for filtering
+      $serialized['publicationStatusId'] = 
$object->getPublicationStatus()->status->id;
+
+      // linking information; NOT indexed
+//        $serialized['parent']['id'] = $object->parentId;
+//        $serialized['parent']['slug'] = $object->parent->slug;
+
+      // use for linking "part of" in results; NOT indexed
+      $root = $object->getCollectionRoot();
+      $serialized['collectionRoot']['id'] = $root->id;
+      $serialized['collectionRoot']['slug'] = $root->slug;
+      $serialized['collectionRoot']['title'] = $root->title;
+
+      // repository information for linking and filtering; indexed
+      if ($repository = $object->getRepository(array('inherit' => true)))
+      {
+        $serialized['repository']['id'] = $repository->id;
+        $serialized['repository']['slug'] = $repository->slug;
+
+        foreach ($objectI18ns as $culture => $objectI18n)
+        {
+          $serialized['repository'][$culture]['name'] = 
$repository->getAuthorizedFormOfName();
+        }
+      }
+
+      // embed digital object information; indexed?
+      if ($digitalObject = $object->getDigitalObject())
+      {
+        $serialized['digitalObject']['mediaTypeId'] = 
$digitalObject->mediaTypeId;
+
+        if ($digitalObject->thumbnail)
+        {
+          $serialized['digitalObject']['thumbnail_FullPath'] = 
$digitalObject->thumbnail->getFullPath();
+        }
+      }
+
+      // embed dates as an array
+      foreach ($object->getDates() as $date)
+      {
+        $save_date = array(
+          'id' => $date->id,
+          'rendered' => 
Qubit::renderDateStartEnd($date->getDate(array('cultureFallback' => true)), 
$date->startDate, $date->endDate),
+          'type' => $date->getType(array('cultureFallback' => 
true))->__toString()
+        );
+
+        if (isset($date->actor))
+        {
+          $save_date['actor'] = $date->actor->__toString();
+        }
+        $serialized['dates'][] = $save_date;
+      }
+
+      foreach ($objectI18ns as $culture => $objectI18n)
+      {
+        $serialized[$culture]['creator'] = 
$object->getCreatorsNameString(array('culture' => $culture));
+        $serialized[$culture]['creatorHistory'] = 
$object->getCreatorsHistoryString(array('culture' => $culture));
+
+        if ($level = $object->getLevelOfDescription())
+        {
+          $serialized[$culture]['levelOfDescription'] = 
$level->getName(array('culture' => $culture));
+        }
+
+        foreach ($object->getNotes() as $note)
+        {
+          $serialized[$culture]['notes'][] = $note->getContent(array('culture' 
=> $culture));
+        }
+
+        foreach ($object->getNameAccessPoints() as $name)
+        {
+          $serialized[$culture]['names'][] = 
$name->object->getAuthorizedFormOfName(array('culture' => $culture));
+        }
+
+        foreach ($object->getSubjectAccessPoints() as $subject)
+        {
+          $term = $subject->getTerm();
+          $serialized[$culture]['subjects'][] = $term->getName(array('culture' 
=> $culture));
+
+          if ($term->otherNames)
+          {
+            foreach ($term->otherNames as $otherName)
+            {
+              $serialized[$culture]['subjects'][] = 
$otherName->getName(array('culture' => $culture));
+            }
+          }
+        }
+
+        foreach ($object->getPlaceAccessPoints() as $place)
+        {
+          $term = $place->getTerm();
+          $serialized[$culture]['places'][] = $term->getName(array('culture' 
=> $culture));
+
+          if ($term->otherNames)
+          {
+            foreach ($term->otherNames as $otherName)
+            {
+              $serialized[$culture]['places'][] = 
$otherName->getName(array('culture' => $culture));
+            }
+          }
+        }
+
+      }
+
+      // get all i18n-ized versions of this object
+      foreach ($objectI18ns as $culture => $objectI18n)
+      {
+        // index all values on the i18n-ized object
+        foreach (self::getI18nFields(get_class($object)) as $method)
+        {
+          $value = call_user_func(array($objectI18n, 'get' . $method));
+          if (!is_null($value))
+          {
+            $serialized[$culture][lcfirst($method)] = $value;
+          }
+        }
+      }
+
+      return $serialized;
+    }
+
+    public static function getI18nFields($class)
+    {
+      // use reflection on i18n object to get property list from class 
constants
+      if (class_exists($class . 'I18n'))
+      {
+        $reflect = new ReflectionClass($class . 'I18n');
+        $i18nFields = $reflect->getConstants();
+
+        // these constants cannot be accessed by __get()
+        unset($i18nFields['DATABASE_NAME']);
+        unset($i18nFields['TABLE_NAME']);
+
+        // id and culture are not used for indexing
+        unset($i18nFields['ID']);
+        unset($i18nFields['CULTURE']);
+
+        return array_map(array('sfInflector', 'camelize'), 
array_map('strtolower', array_keys($i18nFields)));
+      }
+    }
+  }

Modified: 
trunk/plugins/qtElasticSearchPlugin/lib/qtElasticSearchPlugin.class.php
==============================================================================
--- trunk/plugins/qtElasticSearchPlugin/lib/qtElasticSearchPlugin.class.php     
Tue Feb  7 00:27:13 2012        (r10814)
+++ trunk/plugins/qtElasticSearchPlugin/lib/qtElasticSearchPlugin.class.php     
Tue Feb  7 13:38:57 2012        (r10815)
@@ -21,9 +21,9 @@
  * This class is used to provide a singleton object that uses an ElasticSearch 
instance for
  * application indexing, querying, faceting, etc.
  *
- * @package    Qubit
+ * @package    qtElasticSearchPlugin
  * @author     MJ Suhonos <[email protected]>
- * @version    svn:$Id: qtElasticSearchPlugin.class.php 10316 2011-11-14 
22:40:18Z david $
+ * @version    svn:$Id: qtElasticSearchPlugin.class.php 10316 2011-11-14 
22:40:18Z mj $
  */
 
 class qtElasticSearchPlugin
@@ -31,8 +31,14 @@
   // allow disabling search index via boolean flag
   public $disabled = false;
 
+  // allow modifying the batch size
+  public $batchSize;
+
+  private $batchMode = false;
+  private $batchDocs = array();
+
   public $index = null;
-  
+
   /*
    * Enable singleton creation via getInstance()
    */
@@ -51,86 +57,149 @@
   // constructor
   public function __construct()
   {
-    //I don't understand how a heart is a spade
-    //But somehow the vital connection is made
+    $this->batchSize = qtElasticSearchPluginConfiguration::$batchSize;
+
+    // I don't understand how a heart is a spade
+    // But somehow the vital connection is made
     $client = new Elastica_Client(qtElasticSearchPluginConfiguration::$server);
     $this->index = 
$client->getIndex(qtElasticSearchPluginConfiguration::$index);
+
+    $this->initialize();
   }
 
   // destructor
   public function __destruct()
   {
+    // if there are still documents in the batch queue, send them
+    if ($this->batchMode && count($this->batchDocs) > 0)
+    {
+      $this->index->addDocuments($this->batchDocs);
+    }
+
     // I don't understand how the last card is played
     // But somehow the vital connection is made
     $this->index->flush();
   }
 
+  protected function initialize()
+  {
+    try {
+      $this->index->open();
+
+    } catch (Exception $e) {
+
+      // if the index has not been initialized, create it
+      if ($e instanceof Elastica_Exception_Response) {
+        $this->index->create(array(), true);
+      }
+    }
+
+/*
+    // apply type mappings for each indexed object type
+    // TODO: can load these dynamically from the ./model directory
+    foreach (array('QubitInformationObject', 'QubitActor') as $type)
+    {
+      $mapping = new Elastica_Type_Mapping();
+
+      $mapping->setType($this->index->getType($type));
+      $mapping->setProperties(call_user_func(array($type . 'Mapping', 
'getProperties')));
+
+      $mapping->send();
+    }
+*/
+  }
+
   /*
    * Elastica methods
    */
   public function save($object)
   {
-    $type = $this->index->getType(get_class($object));
+    // NB: serializing from the ORM is the most expensive part
+    $type = get_class($object);
 
-    $document = new Elastica_Document($object->id, self::serialize($object));
-    $type->addDocument($document);
+    $document = new Elastica_Document($object->id, $this->serialize($object));
+    $document->setType($type);
 
-    $type->getIndex()->refresh();
-  }
+    if ($this->batchMode)
+    {
+      // add this document to the batch queue
+      $this->batchDocs[] = $document;
 
-  public function delete($object)
-  {
-    $type = $this->index->getType(get_class($object));
+      // if we have a full batch, send in bulk
+      if (count($this->batchDocs) >= $this->batchSize)
+      {
+        $this->index->addDocuments($this->batchDocs);
+        $this->index->refresh();
 
-    $type->deleteById($object->id);
+        $this->batchDocs = array();
+      }
+    }
+    else
+    {
+      $this->index->getType($type)->addDocument($document);
+      $this->index->refresh();
+    }
   }
 
-  public function deleteById($id)
+  public function delete($object)
   {
-    // TODO: handle QubitInformationObject objects?
-    $type = $this->index->getType('QubitActor');
-
-    $type->deleteById($id);
+    $this->index->getType(get_class($object))->deleteById($object->id);
   }
 
   public function parse($querystring)
   {
+    if (empty($querystring))
+    {
+      throw new Exception('No search terms specified.');
+    }
+
     $query = new Elastica_Query_QueryString($querystring);
     $query->setDefaultOperator('AND');
 
     return $query;
   }
 
+  /*
+   * ZSL compatibility methods
+   */
   public function optimize()
   {
     $this->index->optimize();
   }
 
-  /*
-   * ZSL compatibility methods
-   */
   public function enableBatch()
   {
+    $this->batchMode = true;
   }
 
   public function disableBatch()
   {
+    $this->batchMode = false;
   }
 
+  public function deleteById($id)
+  {
+    // TODO: handle QubitInformationObject objects?
+    $this->index->getType('QubitActor')->deleteById($id);
+  }
+
+  /*
+   * ======= END ZSL compatibility methods
+   */
+
   public function qubitPopulate($options)
   {
     
sfContext::createInstance(sfProjectConfiguration::getApplicationConfiguration('qubit',
 'cli', true));
 
-    $conn = Propel::getConnection();
-
     $start = microtime(true);
-    sfContext::getInstance()->getLogger()->log('Populating index...', 
sfLogger::EMERG);
+    $this->logger->log('Populating index...', 'qtElasticSearch');
 
     // if we are using an offset to resume from a segfault, optimize the index 
instead of deleting
     if (!isset($options['actorOffset']) && !isset($options['ioOffset']))
     {
       $this->index->delete();
-      sfContext::getInstance()->getLogger()->log('Index erased.', 
sfLogger::EMERG);
+      $this->initialize();
+      $this->logger->log('Index erased.', 'qtElasticSearch');
     }
     else
     {
@@ -138,7 +207,7 @@
     }
 
     // set buffering and updates to be batched for better performance
-    self::getInstance()->enableBatch();
+    $this->enableBatch();
 
     $actorOffset = intval($options['actorOffset']);
     $ioOffset = intval($options['ioOffset']);
@@ -146,235 +215,84 @@
     // index actors
     if (-1 < $actorOffset)
     {
-      // Get count of all actors
-      $sql = 'SELECT COUNT(*) from '.QubitActor::TABLE_NAME;
-      $rs = $conn->query($sql);
-      $rowcount = $rs->fetchColumn(0);
-
-      // Get actors (with offset)
       $criteria = new Criteria;
-      QubitActor::addSelectColumns($criteria);
+      $criteria = QubitActor::addGetOnlyActorsCriteria($criteria);
+      $criteria->add(QubitActor::ID, QubitActor::ROOT_ID, Criteria::NOT_EQUAL);
 
       if (0 < $actorOffset)
       {
         $criteria->setOffset($actorOffset);
+        $this->logger->log('Ignoring first '.$ioOffset.' actors.', 
'qtElasticSearch');
       }
 
       $actors = QubitActor::get($criteria);
+      $rowcount = count($actors) + $ioOffset;
+      $total = $rowcount;
 
-      // Loop through results, and add to search index
       foreach ($actors as $key => $actor)
       {
-        if ($key == 0 && 0 < $actorOffset)
-        {
-          sfContext::getInstance()->getLogger()->log('Ignoring first 
'.$actorOffset.' actors.');
-        }
-
-        self::save($actor);
-
-        sfContext::getInstance()->getLogger()->log('"'.$actor->__toString().'" 
inserted ('.round(microtime(true) - $start, 2).'s) ('.($key + $actorOffset + 
1).'/'.$rowcount.')', sfLogger::EMERG);
+        $this->save($actor);
+        $this->logger->log('"'.$actor->__toString().'" inserted 
('.round(microtime(true) - $start, 2).'s) ('.($key + $actorOffset + 
1).'/'.$rowcount.')', 'qtElasticSearch');
       }
     }
     else
     {
-      sfContext::getInstance()->getLogger()->log('Actors are ignored.');
+      $this->logger->log('Information objects are ignored.', 
'qtElasticSearch');
     }
 
     // index information objects
     if (-1 < $ioOffset)
     {
-      // Get count of all information objects
-      $sql = 'SELECT COUNT(*) from '.QubitInformationObject::TABLE_NAME;
-      $rs = $conn->query($sql);
-      $rowcount = $rs->fetchColumn(0);
-
-      // Get info objects (with offset)
       $criteria = new Criteria;
-      QubitInformationObject::addSelectColumns($criteria);
+      $criteria->add(QubitInformationObject::ID, 
QubitInformationObject::ROOT_ID, Criteria::NOT_EQUAL);
 
       if (0 < $ioOffset)
       {
         $criteria->setOffset($ioOffset);
+        $this->logger->log('Ignoring first '.$ioOffset.' information 
objects.', 'qtElasticSearch');
       }
 
       $informationObjects = QubitInformationObject::get($criteria);
+      $rowcount = count($informationObjects) + $ioOffset;
+      $total = $total + $rowcount;
 
-      // Loop through results, and add to search index
       foreach ($informationObjects as $key => $informationObject)
       {
-        if ($key == 0 && 0 < $ioOffset)
-        {
-          sfContext::getInstance()->getLogger()->log('Ignoring first 
'.$ioOffset.' information objects.');
-        }
-
-            self::save($informationObject);
-//            self::addInformationObjectIndex($informationObject, $language, 
$options);
-
-        
sfContext::getInstance()->getLogger()->log('"'.$informationObject->__toString().'"
 inserted ('.round(microtime(true) - $start, 2).'s) ('.($key + $ioOffset + 
1).'/'.$rowcount.')', sfLogger::EMERG);
+        $this->save($informationObject);
+        $this->logger->log('"'.$informationObject->__toString().'" inserted 
('.round(microtime(true) - $start, 2).'s) ('.($key + $ioOffset + 
1).'/'.$rowcount.')', 'qtElasticSearch');
       }
     }
     else
     {
-      sfContext::getInstance()->getLogger()->log('Information objects are 
ignored.');
+      $this->logger->log('Information objects are ignored.', 
'qtElasticSearch');
     }
 
-    sfContext::getInstance()->getLogger()->log('Index populated in 
"'.round(microtime(true) - $start, 2).'" seconds.', sfLogger::EMERG);
+    // if there are still documents in the batch queue, send them
+    if ($this->batchMode && count($this->batchDocs) > 0)
+    {
+      $this->index->addDocuments($this->batchDocs);
+      $this->index->refresh();
+      $this->batchDocs = array();
+    }
+
+    $this->logger->log('Index populated with "'.($total).'" documents in 
"'.round(microtime(true) - $start, 2).'" seconds.', 'qtElasticSearch');
   }
 
   /*
-   * take an object and return an associative multidimensional array of 
properties
-   * TODO: object classes should implement a static ::serialize() method
-   * NB: this is incompatible with the PHP Serializable interface, which 
returns a string
-   * http://php.net/manual/en/class.serializable.php
-   */
+  * NB: object classes should implement a static ::serialize() method, which 
returns a
+  * JSON-encoded string of the multi-array compatible with the PHP 
Serializable interface
+  *
+  * http://php.net/manual/en/class.serializable.php
+  */
   public function serialize($object)
   {
-    $serialized = array();
-    $class = get_class($object);
-
-    // use reflection on i18n object to get property list from class constants
-    if (class_exists($class . 'I18n'))
-    {
-      $reflect = new ReflectionClass($class . 'I18n');
-      $i18nMethods = $reflect->getConstants();
-
-      // these constants cannot be accessed by __get()
-      unset($i18nMethods['DATABASE_NAME']);
-      unset($i18nMethods['TABLE_NAME']);
-
-      $i18nMethods = array_map(array('sfInflector', 'camelize'), 
array_map('strtolower', array_keys($i18nMethods)));
-
-      foreach ($i18nMethods as $method)
-      {
-        // TODO: implement i18n fallback / default culture
-        // NB: 'id' and 'culture' are default (ie. non-i18n) fields
-        $value = call_user_func(array($object, 'get' . $method));
-        if (!is_null($value))
-        {
-          $serialized[lcfirst($method)] = $value;
-        }
-      }
-    }
-
-    // general serializations
-    $serialized['className'] = $class;  // NB: not required with ES; use type 
instead
-
-    // class-specific serializations
-    switch ($class)
+    // take an object and return an associative multidimensional array of 
properties
+    if (class_exists(get_class($object) . 'Mapping'))
     {
-      case 'QubitInformationObject':
-        $serialized['slug'] = $object->slug;
-        $serialized['parentId'] = $object->parentId;
-        $serialized['parent'] = $object->parent->slug;
-
-        $root = $object->getCollectionRoot();
-        $serialized['collectionRoot']['id'] = $root->id;
-        $serialized['collectionRoot']['slug'] = $root->slug;
-        $serialized['collectionRoot']['title'] = $root->title;
-
-        $serialized['referenceCode'] = $object->referenceCode;
-        $serialized['identifier'] = $object->getIdentifier();
-        $serialized['creator'] = $object->getCreatorsNameString();
-        $serialized['creatorHistory'] = $object->getCreatorsHistoryString();
-        $serialized['publicationStatusId'] = 
$object->getPublicationStatus()->status->id;
-
-        if ($level = $object->getLevelOfDescription())
-        {
-          $serialized['levelOfDescription'] = $level->getName();
-        }
-
-        // embed repository information
-        if ($repository = $object->getRepository(array('inherit' => true)))
-        {
-          $serialized['repository']['id'] = $repository->id;
-          $serialized['repository']['slug'] = $repository->slug;
-          $serialized['repository']['name'] = 
$repository->getAuthorizedFormOfName();
-        }
-
-        // embed digital object information
-        if ($digitalObject = $object->getDigitalObject())
-        {
-          $serialized['digitalObject']['mediaTypeId'] = 
$digitalObject->mediaTypeId;
-
-          if ($digitalObject->thumbnail)
-          {
-            $serialized['digitalObject']['thumbnail_FullPath'] = 
$digitalObject->thumbnail->getFullPath();
-          }
-        }
-
-        // embed dates as an array
-        foreach ($object->getDates() as $date)
-        {
-          $save_date = array(
-            'id' => $date->id,
-            'rendered' => 
Qubit::renderDateStartEnd($date->getDate(array('cultureFallback' => true)), 
$date->startDate, $date->endDate),
-            'type' => $date->getType(array('cultureFallback' => 
true))->__toString()
-          );
-
-          if (isset($date->actor))
-          {
-            $save_date['actor'] = $date->actor->__toString();
-          }
-          $serialized['dates'][] = $save_date;
-        }
-
-        foreach ($object->getNotes() as $note)
-        {
-          $serialized['notes'][] = $note->getContent();
-        }
-
-        foreach ($object->getNameAccessPoints() as $name)
-        {
-          $serialized['names'][] = $name->object->getAuthorizedFormOfName();
-        }
-
-        foreach ($object->getSubjectAccessPoints() as $subject)
-        {
-          $term = $subject->getTerm();
-          $serialized['subjects'][] = $term->getName();
-
-          if ($term->otherNames)
-          {
-            foreach ($term->otherNames as $otherName)
-            {
-              $serialized['subjects'][] = $otherName->getName();
-            }
-          }
-        }
-
-        foreach ($object->getPlaceAccessPoints() as $place)
-        {
-          $term = $place->getTerm();
-          $serialized['places'][] = $term->getName();
-
-          if ($term->otherNames)
-          {
-            foreach ($term->otherNames as $otherName)
-            {
-              $serialized['places'][] = $otherName->getName();
-            }
-          }
-        }
-
-        break;
-
-      case 'QubitActor':
-
-        // Add other forms of name
-        $criteria = new Criteria;
-        $criteria->addJoin(QubitOtherNameI18n::ID, QubitOtherName::ID);
-        $criteria->add(QubitOtherName::OBJECT_ID, $object->id);
-
-        $otherNames = QubitOtherNameI18n::get($criteria);
-
-        foreach ($otherNames as $otherName)
-        {
-          $serialized['otherNames'][] = $otherName->name;
-        }
-
-        break;
+      $serialized = call_user_func_array(array(get_class($object) . 'Mapping', 
'serialize'), array($object));
     }
 
+    // TODO: trim empty/null/blank elements in the array
     return $serialized;
   }
 

Modified: 
trunk/plugins/qtElasticSearchPlugin/modules/search/actions/indexAction.class.php
==============================================================================
--- 
trunk/plugins/qtElasticSearchPlugin/modules/search/actions/indexAction.class.php
    Tue Feb  7 00:27:13 2012        (r10814)
+++ 
trunk/plugins/qtElasticSearchPlugin/modules/search/actions/indexAction.class.php
    Tue Feb  7 13:38:57 2012        (r10815)
@@ -47,12 +47,12 @@
       return;
     }
 
-//  TODO: not implemented yet
-//    $query = $this->filterQuery($query);
+    $query = $this->filterQuery($query);
+    $query = $this->facetQuery($query);
 
     try
     {
-      $hits = 
QubitSearch::getInstance()->index->getType('QubitInformationObject')->search($query)->getResults();
+      $resultSet = 
QubitSearch::getInstance()->index->getType('QubitInformationObject')->search($query);
     }
     catch (Exception $e)
     {
@@ -61,21 +61,22 @@
       return;
     }
 
-    if (!empty($hits))
+    if (0 < $resultSet->getTotalHits())
     {
-      $this->pager = new QubitArrayPager;
+      // mock up a QubitPager for partial template backward compatibility
+      $this->pager = new stdClass();
 
       if ('print' != $request->getGetParameter('media'))
       {
-        $this->pager->setMaxPerPage($request->limit);
+        $this->pager->maxPerPage = $request->limit;
       }
       else
       {
-        $this->pager->setMaxPerPage(500); // force for print
+        $this->pager->maxPerPage = 500; // force for print
       }
 
-      $this->pager->hits = $hits;
-      $this->pager->setPage($request->page);
+      $this->pager->resultSet = $resultSet;
+      $this->pager->page = $request->page ? $request->page : 1;
     }
     else if (empty($this->error))
     {
@@ -98,18 +99,24 @@
       return null;
     }
 
-//    $query = new Elastica_Query_Bool();
-//    $query->addMust($queryParsed);
     $query = new Elastica_Query($queryParsed);
-    $query->setLimit(500);
+
+    // set paging for request
+    $query->setLimit($this->request->limit);
+
+    if (!empty($this->request->page))
+    {
+      $query->setFrom(($this->request->page - 1) * $this->request->limit);
+    }
 
     return $query;
   }
 
   public function filterQuery($query)
   {
+/*
     // Limit search to current culture and info. objects
-    
$query->addSubquery(QubitSearch::getInstance()->addTerm('QubitInformationObject',
 'className'), true);
+//    
$query->addSubquery(QubitSearch::getInstance()->addTerm('QubitInformationObject',
 'className'), true);
     
$query->addSubquery(QubitSearch::getInstance()->addTerm($this->context->user->getCulture(),
 'culture'), true);
 
     $query = QubitAcl::searchFilterByRepository($query, 'read');
@@ -122,6 +129,22 @@
       $this->title .= $this->context->i18n->__(' in %1%', array('%1%' => 
$this->getRoute()->resource->authorizedFormOfName));
     }
 
+//    $query = new Elastica_Query_Bool();
+//    $query->addMust($queryParsed);
+
+*/
+    return $query;
+  }
+
+  public function facetQuery($query)
+  {
+/*
+    // TODO: add facets
+    $facet = new Elastica_Facet_Terms('en.title');
+    $facet->setField('en.title');
+    $facet->setSize(10);
+    //    $query->addFacet($facet);
+*/
     return $query;
   }
 }

Modified: 
trunk/plugins/qtElasticSearchPlugin/modules/search/templates/_searchResults.php
==============================================================================
--- 
trunk/plugins/qtElasticSearchPlugin/modules/search/templates/_searchResults.php 
    Tue Feb  7 00:27:13 2012        (r10814)
+++ 
trunk/plugins/qtElasticSearchPlugin/modules/search/templates/_searchResults.php 
    Tue Feb  7 13:38:57 2012        (r10815)
@@ -1,26 +1,27 @@
 <div id="search-stats">
-  <?php if (0 < $pager->getNbResults()): ?>
+  <?php if (0 < $pager->resultSet->getTotalHits()): ?>
     <?php echo __('Showing results %1% to %2% of %3% (%4% seconds)', array(
-      '%1%' => $pager->getFirstIndice(),
-      '%2%' => $pager->getLastIndice(),
-      '%3%' => $pager->getNbResults(),
+      '%1%' => (1 + ($pager->page - 1) * $pager->maxPerPage),
+      '%2%' => min($pager->maxPerPage * $pager->page, 
$pager->resultSet->getTotalHits()),
+      '%3%' => $pager->resultSet->getTotalHits(),
       '%4%' => $timer->elapsed()
       )) ?>
   <?php else: ?>
     <?php echo __('No results') ?>
   <?php endif; ?>
-  <?php if ('print' == $sf_request->getGetParameter('media') && 
$pager->haveToPaginate()): ?>
+  <?php if ('print' == $sf_request->getGetParameter('media') && 
$pager->resultSet->getTotalHits() > $pager->maxPerPage): ?>
     <?php echo __('(Only showing first %1% results for performance reasons)',
       array('%1%' => $pager->getMaxPerPage())) ?>
   <?php endif; ?>
 </div>
 
 <div class="section">
-  <?php foreach ($pager->getResults() as $hit): ?>
+  <?php foreach ($pager->resultSet->getResults() as $hit): ?>
     <?php $doc = $hit->getData(); ?>
+
     <div class="clearfix search-results <?php echo 0 == @++$row % 2 ? 'even' : 
'odd' ?>">
 
-      <?php if (isset($doc['digitalObject'])): ?>
+      <?php if (isset($doc['digitalObject'])):/* ?>
         <?php if ($doc['digitalObject']['mediaTypeId']): ?>
           <?php echo 
link_to(image_tag(QubitDigitalObject::getGenericRepresentation($doc['digitalObject']['mediaTypeId'],
 QubitTerm::THUMBNAIL_ID)->getFullPath(), array('alt' => $doc['title'])), 
array('slug' => $doc['slug'], 'module' => 'informationobject')) ?>
         <?php elseif (QubitTerm::AUDIO_ID == 
$doc['digitalObject']['mediaTypeId']): ?>
@@ -28,13 +29,13 @@
         <?php elseif (null !== $doc['digitalObject']['thumbnail_FullPath']): ?>
           <?php echo 
link_to(image_tag(public_path($doc['digitalObject']['thumbnail_FullPath']), 
array('alt' => $doc['title'])), array('slug' => $doc['slug'], 'module' => 
'informationobject')) ?>
         <?php endif;?>
-      <?php endif; ?>
+      <?php */ endif; ?>
 
-      <h2><?php echo link_to($doc['title'], array('slug' => $doc['slug'], 
'module' => 'informationobject')) ?><?php if 
(QubitTerm::PUBLICATION_STATUS_DRAFT_ID == $doc['publicationStatusId']): ?> 
<span class="publicationStatus">draft</span><?php endif; ?></h2>
+      <h2><?php echo link_to($doc['en']['title'], array('slug' => 
$doc['slug'], 'module' => 'informationobject')) ?><?php if 
(QubitTerm::PUBLICATION_STATUS_DRAFT_ID == $doc['publicationStatusId']): ?> 
<span class="publicationStatus">draft</span><?php endif; ?></h2>
 
-      <?php if ($doc['scopeAndContent']): ?>
+      <?php if ($doc['en']['scopeAndContent']): ?>
         <div class="field">
-          <?php echo highlight_text(truncate_text($doc['scopeAndContent'], 
256), $sf_request->query) ?>
+          <?php echo 
highlight_text(truncate_text($doc['en']['scopeAndContent'], 256), 
$sf_request->query) ?>
         </div>
       <?php endif; ?>
 
@@ -67,12 +68,12 @@
         <?php echo render_show(__('Level of description'), 
render_value($doc['levelOfDescription'])) ?>
       <?php endif; ?>
 
-      <?php if (sfConfig::get('app_multi_repository') && null != 
isset($doc['repositoryId'])): ?>
-        <?php echo render_show(__('Repository'), 
link_to($doc['repositoryName'], array('slug' => $doc['repositorySlug'], 
'module' => 'repository'))) ?>
+      <?php if (sfConfig::get('app_multi_repository') && null != 
isset($doc['repository']['id'])): ?>
+        <?php echo render_show(__('Repository'), 
link_to($doc['repository']['en']['name'], array('slug' => 
$doc['repository']['slug'], 'module' => 'repository'))) ?>
       <?php endif; ?>
 
-      <?php if (isset($doc['collectionRootId']) && $doc['collectionRootId'] 
!== $doc['id']): ?>
-        <?php echo render_show(__('Part of'), 
link_to($doc['collectionRootTitle'], array('slug' => 
$doc['collectionRootSlug'], 'module' => 'informationobject'))) ?>
+      <?php if (isset($doc['collectionRoot']['id']) && 
$doc['collectionRoot']['id'] !== $hit->getId()): ?>
+        <?php echo render_show(__('Part of'), 
link_to($doc['collectionRoot']['title'], array('slug' => 
$doc['collectionRoot']['slug'], 'module' => 'informationobject'))) ?>
       <?php endif; ?>
 
     </div>
@@ -80,5 +81,66 @@
 </div>
 
 <?php if ('print' != $sf_request->getGetParameter('media')): ?>
-  <?php echo get_partial('default/pager', array('pager' => $pager)) ?>
+
+<div class="result-count">
+  <?php if (0 < $pager->resultSet->getTotalHits()): ?>
+  <?php echo __('Results %1% to %2% of %3%',
+        array('%1%' => (1 + ($pager->page - 1) * $pager->maxPerPage),
+              '%2%' => min($pager->maxPerPage * $pager->page, 
$pager->resultSet->getTotalHits()),
+              '%3%' => $pager->resultSet->getTotalHits())) ?>
+  <?php else: ?>
+  <?php echo __('No results') ?>
+  <?php endif; ?>
+</div>
+
+<?php if ($pager->resultSet->getTotalHits() > $pager->maxPerPage): ?>
+  <div class="pager section">
+
+    <h2 class="element-invisible"><?php echo __('Pages') ?></h2>
+
+    <div class="content">
+
+      <?php if (1 < $pager->page): ?>
+      <?php echo link_to(__('Previous'), array('page' => $pager->page - 1) + 
$sf_request->getParameterHolder()->getAll(), array('rel' => 'prev', 'title' => 
__('Go to previous page'))) ?>
+      <?php endif; ?>
+
+      <ol>
+        <?php /*foreach ($pager->getLinks(10) as $page): ?>
+        <?php if ($pager->page == $page): ?>
+          <li class="active"><?php echo $page ?></li>
+          <?php else: ?>
+          <li><?php echo link_to($page, array('page' => $page) + 
$sf_request->getParameterHolder()->getAll(), array('title' => __('Go to page 
%1%', array('%1%' => $page)))) ?></li>
+          <?php endif; ?>
+        <?php endforeach */?>
+      </ol>
+
+      <?php if ($pager->resultSet->getTotalHits() > ($pager->maxPerPage * 
$pager->page)): ?>
+      <?php echo link_to(__('Next'), array('page' => $pager->page + 1) + 
$sf_request->getParameterHolder()->getAll(), array('rel' => 'next', 'title' => 
__('Go to next page'))) ?>
+      <?php endif; ?>
+
+    </div>
+
+  </div>
+  <?php endif; ?>
+
+<?php if (10 < $pager->resultSet->getTotalHits()): ?>
+  <div class="itemsPerPage section">
+    <?php ob_start() ?>
+    <ol>
+      <?php foreach (array(10, 50, 100, 500) as $limit): ?>
+
+      <?php if ($sf_request->limit == $limit): ?>
+        <li class="active"><?php echo $limit ?></li>
+        <?php else: ?>
+        <li><?php echo link_to($limit, array('limit' => $limit) + 
$sf_request->getParameterHolder()->getAll(), array('title' => __('%1% results 
per page', array('%1%' => $limit)))) ?></li>
+        <?php endif; ?>
+
+      <?php if ($pager->resultSet->getTotalHits() < $limit) break; ?>
+
+      <?php endforeach; ?>
+    </ol>
+    <?php echo __('%1% results per page', array('%1%' => ob_get_clean())) ?>
+  </div>
+  <?php endif; ?>
+
 <?php endif; ?>

-- 
You received this message because you are subscribed to the Google Groups 
"Qubit Toolkit Commits" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/qubit-commits?hl=en.

Reply via email to