From 533386d53e2138937be31606f37e9230e1a000fd Mon Sep 17 00:00:00 2001
From: pbarroca <pbarroca@git-test.afi-sa.fr>
Date: Fri, 18 May 2012 15:09:43 +0000
Subject: [PATCH] =?UTF-8?q?OPDS:=20Int=C3=A9gration=20de=20la=20recherche?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitattributes                                |  1 +
 .../admin/controllers/OpdsController.php      |  9 +++
 .../admin/views/scripts/opds/browse.phtml     |  1 +
 library/Class/OpdsCatalog.php                 | 19 ++++-
 .../Class/WebService/OPDS/CatalogReader.php   | 57 +++++++++++++--
 .../Class/WebService/OPDS/CatalogSearch.php   | 71 +++++++++++++++++++
 .../admin/controllers/OpdsControllerTest.php  | 45 ++++++++++++
 7 files changed, 196 insertions(+), 7 deletions(-)
 create mode 100644 library/Class/WebService/OPDS/CatalogSearch.php

diff --git a/.gitattributes b/.gitattributes
index fc6fe172142..b048ca9beee 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1804,6 +1804,7 @@ library/Class/WebService/OAI/ResponseFactory.php -text
 library/Class/WebService/OAI/ResumptionToken.php -text
 library/Class/WebService/OPDS/CatalogEntry.php -text
 library/Class/WebService/OPDS/CatalogReader.php -text
+library/Class/WebService/OPDS/CatalogSearch.php -text
 library/Class/WebService/Premiere.php -text
 library/Class/WebService/ReseauxSociaux.php -text
 library/Class/WebService/ResumptionToken.php -text
diff --git a/application/modules/admin/controllers/OpdsController.php b/application/modules/admin/controllers/OpdsController.php
index 86682732322..d16badcd7b5 100644
--- a/application/modules/admin/controllers/OpdsController.php
+++ b/application/modules/admin/controllers/OpdsController.php
@@ -87,6 +87,15 @@ class Admin_OpdsController extends Zend_Controller_Action {
 			return;
 		}
 
+		if ($this->_request->isPost()
+				&& $catalog->hasSearch()
+				&& ($form = $catalog->getSearchForm())
+				&& $form->isValid($this->_request->getPost())) {
+			$this->_redirect('/admin/opds/browse/id/' . $catalog->getId() . '?entry=' . urlencode($catalog->getSearch()->entryForTerm($form->getValue('search'))));
+			return;
+		}
+
+
 		if ($entry_url = $this->_getParam('entry'))
 			$catalog = $catalog->newForEntry($entry_url);
 
diff --git a/application/modules/admin/views/scripts/opds/browse.phtml b/application/modules/admin/views/scripts/opds/browse.phtml
index 1263b531431..ed6d1d7b9a2 100644
--- a/application/modules/admin/views/scripts/opds/browse.phtml
+++ b/application/modules/admin/views/scripts/opds/browse.phtml
@@ -7,6 +7,7 @@
 		<?php } ?>
 	</ul>
 	<?php } ?>
+		 <?php if ($this->catalog->hasSearch()) echo $this->catalog->getSearchForm();?>
 </div>
 <ul>
 	<?php foreach($this->catalog->getEntries() as $entry) { ?>
diff --git a/library/Class/OpdsCatalog.php b/library/Class/OpdsCatalog.php
index 604b9c810f7..dccdf52b884 100644
--- a/library/Class/OpdsCatalog.php
+++ b/library/Class/OpdsCatalog.php
@@ -84,9 +84,26 @@ class Class_OpdsCatalog extends Storm_Model_Abstract {
 		return $this;
 	}
 
-}
+
+	public function hasSearch() {
+		return $this->getCatalogueReader()->hasSearch();
+	}
 
 
+	public function getSearchForm() {
+		$form = new Zend_Form(array('method' => 'post'));
+		$form
+			->addElement('text', 'search', array('required' => true,
+																					 'allowEmpty' => false))
+			->addElement('submit', 'Rechercher');
+		return $form;
+	}
 
 
+	public function getSearch() {
+		return $this->getCatalogueReader()
+			->getSearch()
+			->setWebClient($this->_web_client);
+	}
+}
 ?>
\ No newline at end of file
diff --git a/library/Class/WebService/OPDS/CatalogReader.php b/library/Class/WebService/OPDS/CatalogReader.php
index d0bb1df86dc..0ae1d50f2e3 100644
--- a/library/Class/WebService/OPDS/CatalogReader.php
+++ b/library/Class/WebService/OPDS/CatalogReader.php
@@ -23,6 +23,9 @@ class Class_WebService_OPDS_CatalogReader {
 	protected $_current_entry;
 	protected $_entries;
 	protected $_metadatas;
+	protected $_searchUrl;
+	protected $_selfUrl;
+	protected $_search;
 	protected $_xml_parser;
 
 	public static function fromXML($xml) {
@@ -32,8 +35,9 @@ class Class_WebService_OPDS_CatalogReader {
 
 
 	public function parse($xml) {
-		$this->_entries = array();
-		$this->_metadatas = array();
+		$this->_entries = $this->_metadatas = array();
+		$this->_search = $this->_searchUrl = $this->_selfUrl = null;
+
 		$this->_xml_parser = Class_WebService_XMLParser::newInstance();
 		$this->_xml_parser->setElementHandler($this);
 		$this->_xml_parser->parse($xml);
@@ -51,6 +55,20 @@ class Class_WebService_OPDS_CatalogReader {
 	}
 
 
+	public function getSearch() {
+		if (null != $this->_search) 
+			return $this->_search;
+
+		if (null != $this->_searchUrl)
+			return $this->_search = new Class_WebService_OPDS_CatalogSearch($this->_normalizeUrl($this->_searchUrl));
+	}
+
+
+	public function hasSearch() {
+		return null !== $this->getSearch();
+	}
+
+
 	public function startEntry() {
 		$this->_current_entry = new Class_WebService_OPDS_CatalogEntry();
 		$this->_entries[] = $this->_current_entry;
@@ -68,11 +86,23 @@ class Class_WebService_OPDS_CatalogReader {
 
 
 	public function startLink($attributes) {
-		if (!$this->_xml_parser->inParents('entry'))
+		if (!array_key_exists('TYPE', $attributes)) 
 			return;
 
+		if (!$this->_xml_parser->inParents('entry')) {
+			if (array_key_exists('REL', $attributes)
+					&& 'search' == $attributes['REL']
+					&& 'application/opensearchdescription+xml' == $attributes['TYPE'])
+				$this->_searchUrl = $attributes['HREF'];
+
+			if (array_key_exists('REL', $attributes)
+					&& 'self' == $attributes['REL']
+					&& 'application/atom+xml' == $attributes['TYPE'])
+				$this->_selfUrl = $attributes['HREF'];
+			return;
+		}
+
 		if (array_key_exists('REL', $attributes)
-				&& array_key_exists('TYPE', $attributes)
 				&& 'http://opds-spec.org/acquisition' == $attributes['REL']
 				&& in_array($attributes['TYPE'], array('application/epub+zip', 'application/pdf'))) {
 			$this->_current_entry->addFile($attributes['HREF'], $attributes['TYPE']);
@@ -134,8 +164,23 @@ class Class_WebService_OPDS_CatalogReader {
 
 	protected function _concatMetadata($name, $value, $separator = ' - ') {
 		$this->_metadatas[$name] = (isset($this->_metadatas[$name])) 
-				? $this->_metadatas[$name] . $separator . $value
-				: $value;
+			? $this->_metadatas[$name] . $separator . $value
+			: $value;
+	}
+
+
+	protected function _normalizeUrl($url) {
+		if (!$this->_selfUrl) 
+			return $url;
+
+		if ('http' == substr($url, 0, 4)) 
+			return $url;
+
+		$urlInfos = parse_url($this->_selfUrl);
+		$normalized = $urlInfos['scheme'] . '://' . $urlInfos['host'];
+		if ('/' == substr($url, 0, 1)) 
+			return $normalized . $url;
+		return $normalized . dirname($urlInfos['path']) . $url;
 	}
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/WebService/OPDS/CatalogSearch.php b/library/Class/WebService/OPDS/CatalogSearch.php
new file mode 100644
index 00000000000..c5ea4bc78db
--- /dev/null
+++ b/library/Class/WebService/OPDS/CatalogSearch.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * AFI-OPAC 2.0 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ */
+class Class_WebService_OPDS_CatalogSearch {
+	const TOKEN = '{searchTerms}';
+
+	protected $_url;
+	protected $_web_client;
+	protected $_xml_parser;
+	protected $_template;
+
+	public function __construct($url) {
+		$this->_url = $url;
+	}
+
+
+	public function entryForTerm($term) {
+		$this->_template = null;
+		$xml = $this->getWebClient()->open_url($this->_url);
+		$this->_xml_parser = Class_WebService_XMLParser::newInstance();
+		$this->_xml_parser->setElementHandler($this);
+		$this->_xml_parser->parse($xml);
+
+		if (null == $this->_template)
+			return;
+
+		return str_replace(self::TOKEN, urlencode($term), $this->_template);
+	}
+
+
+	public function startUrl($attributes) {
+		if (!array_key_exists('TYPE', $attributes)
+				|| !array_key_exists('TEMPLATE', $attributes))
+			return;
+
+		if ('application/atom+xml' != $attributes['TYPE']) 
+			return;
+
+		$this->_template = $attributes['TEMPLATE'];
+	}
+
+
+	public function setWebClient($client) {
+		$this->_web_client = $client;
+		return $this;
+	}
+
+
+	public function getWebClient() {
+		if (null != $this->_web_client)
+			return $this->_web_client;
+		return new Class_WebService_SimpleWebClient();
+	}
+}
diff --git a/tests/application/modules/admin/controllers/OpdsControllerTest.php b/tests/application/modules/admin/controllers/OpdsControllerTest.php
index 8661d533ed0..7e0577df2c5 100644
--- a/tests/application/modules/admin/controllers/OpdsControllerTest.php
+++ b/tests/application/modules/admin/controllers/OpdsControllerTest.php
@@ -387,6 +387,12 @@ class Admin_OpdsControllerBrowseActionTest extends Admin_OpdsControllerBrowseEbo
 	}
 
 
+	/** @test */
+	public function searchFieldShouldBePresent() {
+		$this->assertXPath('//div[@class="panel"]//input[@name="search"]');
+	}
+
+
 	/** @test */
 	public function linkToLastPublishedShouldBePresent() {
 		$this->assertXPathContentContains('//a[contains(@href, "/browse/id/1?entry=' . urlencode('http://www.ebooksgratuits.com/opds/feed.php') . '")]', 
@@ -486,6 +492,28 @@ class Admin_OpdsControllerBrowseEbooksGratuitsImportTest extends Admin_OpdsContr
 
 
 
+class Admin_OpdsControllerBrowseSearchPostActionTest extends Admin_OpdsControllerBrowseEbooksGratuitsTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		Class_OpdsCatalog::getLoader()->find(1)
+			->getWebClient()
+			->whenCalled('open_url')
+			->with('http://www.ebooksgratuits.com/opds/opensearch.xml')
+			->answers(OPDSFeedFixtures::ebooksGratuitsSearchDescriptionXml());
+
+		$this->postDispatch('/admin/opds/browse/id/1', array('search' => 'dracula'));
+	}
+
+
+	/** @test */
+	public function shouldRedirectToBrowseWithSearchEntry() {
+		$this->assertRedirectTo('/admin/opds/browse/id/1?entry=' . urlencode('http://www.ebooksgratuits.com/opds/feed.php?mode=search&query=dracula'));
+	}
+}
+
+
+
 class OPDSFeedFixtures {
 	public static function ebooksGratuitsStartXml() {
 		return '<?xml version="1.0" encoding="UTF-8"?>
@@ -615,5 +643,22 @@ class OPDSFeedFixtures {
 	</feed>';
 	}
 	
+
+
+	public static function ebooksGratuitsSearchDescriptionXml() {
+		return '<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+  <ShortName>Ebooksgratuits</ShortName>
+  <Description>Recherche d\'e-books sur ebooksgratuits</Description>
+  <InputEncoding>UTF-8</InputEncoding>
+  <OutputEncoding>UTF-8</OutputEncoding>
+  <Image type="image/x-icon" width="16" height="16">http://www.ebooksgratuits.com/favicon.ico</Image>
+
+  <Url type="text/html" template="http://www.ebooksgratuits.com/ebooks.php?titre={searchTerms}"/>
+  <Url type="application/atom+xml" template="http://www.ebooksgratuits.com/opds/feed.php?mode=search&amp;query={searchTerms}"/>
+  <Url type="application/x-suggestions+json" rel="suggestions" template="http://www.ebooksgratuits.com/opds/search.php?mode=json&amp;query={searchTerms}"/>
+  <Query role="example" searchTerms="robot" />
+</OpenSearchDescription>';
+	}
 }
 ?>
\ No newline at end of file
-- 
GitLab