+ * ...
+ * $document->title = 'Something';
+ * echo $document->title;
+ * ...
+ *
+ *
+ * Additionally, the field values can be iterated with foreach
+ *
+ *
+ * foreach ($document as $fieldName => $fieldValue)
+ * {
+ * ...
+ * }
+ *
+ */
+class Apache_Solr_Document implements IteratorAggregate
+{
+ /**
+ * SVN Revision meta data for this class
+ */
+ const SVN_REVISION = '$Revision: 54 $';
+
+ /**
+ * SVN ID meta data for this class
+ */
+ const SVN_ID = '$Id: Document.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
+
+ /**
+ * Document boost value
+ *
+ * @var float
+ */
+ protected $_documentBoost = false;
+
+ /**
+ * Document field values, indexed by name
+ *
+ * @var array
+ */
+ protected $_fields = array();
+
+ /**
+ * Document field boost values, indexed by name
+ *
+ * @var array array of floats
+ */
+ protected $_fieldBoosts = array();
+
+ /**
+ * Clear all boosts and fields from this document
+ */
+ public function clear()
+ {
+ $this->_documentBoost = false;
+
+ $this->_fields = array();
+ $this->_fieldBoosts = array();
+ }
+
+ /**
+ * Get current document boost
+ *
+ * @return mixed will be false for default, or else a float
+ */
+ public function getBoost()
+ {
+ return $this->_documentBoost;
+ }
+
+ /**
+ * Set document boost factor
+ *
+ * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
+ */
+ public function setBoost($boost)
+ {
+ $boost = (float) $boost;
+
+ if ($boost > 0.0)
+ {
+ $this->_documentBoost = $boost;
+ }
+ else
+ {
+ $this->_documentBoost = false;
+ }
+ }
+
+ /**
+ * Add a value to a multi-valued field
+ *
+ * NOTE: the solr XML format allows you to specify boosts
+ * PER value even though the underlying Lucene implementation
+ * only allows a boost per field. To remedy this, the final
+ * field boost value will be the product of all specified boosts
+ * on field values - this is similar to SolrJ's functionality.
+ *
+ *
+ * $doc = new Apache_Solr_Document();
+ *
+ * $doc->addField('foo', 'bar', 2.0);
+ * $doc->addField('foo', 'baz', 3.0);
+ *
+ * // resultant field boost will be 6!
+ * echo $doc->getFieldBoost('foo');
+ *
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
+ */
+ public function addField($key, $value, $boost = false)
+ {
+ if (!isset($this->_fields[$key]))
+ {
+ // create holding array if this is the first value
+ $this->_fields[$key] = array();
+ }
+ else if (!is_array($this->_fields[$key]))
+ {
+ // move existing value into array if it is not already an array
+ $this->_fields[$key] = array($this->_fields[$key]);
+ }
+
+ if ($this->getFieldBoost($key) === false)
+ {
+ // boost not already set, set it now
+ $this->setFieldBoost($key, $boost);
+ }
+ else if ((float) $boost > 0.0)
+ {
+ // multiply passed boost with current field boost - similar to SolrJ implementation
+ $this->_fieldBoosts[$key] *= (float) $boost;
+ }
+
+ // add value to array
+ $this->_fields[$key][] = $value;
+ }
+
+ /**
+ * Handle the array manipulation for a multi-valued field
+ *
+ * @param string $key
+ * @param string $value
+ * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
+ *
+ * @deprecated Use addField(...) instead
+ */
+ public function setMultiValue($key, $value, $boost = false)
+ {
+ $this->addField($key, $value, $boost);
+ }
+
+ /**
+ * Get field information
+ *
+ * @param string $key
+ * @return mixed associative array of info if field exists, false otherwise
+ */
+ public function getField($key)
+ {
+ if (isset($this->_fields[$key]))
+ {
+ return array(
+ 'name' => $key,
+ 'value' => $this->_fields[$key],
+ 'boost' => $this->getFieldBoost($key)
+ );
+ }
+
+ return false;
+ }
+
+ /**
+ * Set a field value. Multi-valued fields should be set as arrays
+ * or instead use the addField(...) function which will automatically
+ * make sure the field is an array.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
+ */
+ public function setField($key, $value, $boost = false)
+ {
+ $this->_fields[$key] = $value;
+ $this->setFieldBoost($key, $boost);
+ }
+
+ /**
+ * Get the currently set field boost for a document field
+ *
+ * @param string $key
+ * @return float currently set field boost, false if one is not set
+ */
+ public function getFieldBoost($key)
+ {
+ return isset($this->_fieldBoosts[$key]) ? $this->_fieldBoosts[$key] : false;
+ }
+
+ /**
+ * Set the field boost for a document field
+ *
+ * @param string $key field name for the boost
+ * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false
+ */
+ public function setFieldBoost($key, $boost)
+ {
+ $boost = (float) $boost;
+
+ if ($boost > 0.0)
+ {
+ $this->_fieldBoosts[$key] = $boost;
+ }
+ else
+ {
+ $this->_fieldBoosts[$key] = false;
+ }
+ }
+
+ /**
+ * Return current field boosts, indexed by field name
+ *
+ * @return array
+ */
+ public function getFieldBoosts()
+ {
+ return $this->_fieldBoosts;
+ }
+
+ /**
+ * Get the names of all fields in this document
+ *
+ * @return array
+ */
+ public function getFieldNames()
+ {
+ return array_keys($this->_fields);
+ }
+
+ /**
+ * Get the values of all fields in this document
+ *
+ * @return array
+ */
+ public function getFieldValues()
+ {
+ return array_values($this->_fields);
+ }
+
+ /**
+ * IteratorAggregate implementation function. Allows usage:
+ *
+ *
+ * foreach ($document as $key => $value)
+ * {
+ * ...
+ * }
+ *
+ */
+ public function getIterator()
+ {
+ $arrayObject = new ArrayObject($this->_fields);
+
+ return $arrayObject->getIterator();
+ }
+
+ /**
+ * Magic get for field values
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (isset($this->_fields[$key]))
+ {
+ return $this->_fields[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Magic set for field values. Multi-valued fields should be set as arrays
+ * or instead use the addField(...) function which will automatically
+ * make sure the field is an array.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function __set($key, $value)
+ {
+ $this->setField($key, $value);
+ }
+
+ /**
+ * Magic isset for fields values. Do not call directly. Allows usage:
+ *
+ *
+ * isset($document->some_field);
+ *
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function __isset($key)
+ {
+ return isset($this->_fields[$key]);
+ }
+
+ /**
+ * Magic unset for field values. Do not call directly. Allows usage:
+ *
+ *
+ * unset($document->some_field);
+ *
+ *
+ * @param string $key
+ */
+ public function __unset($key)
+ {
+ unset($this->_fields[$key]);
+ unset($this->_fieldBoosts[$key]);
+ }
+}
\ No newline at end of file
diff --git a/Apache/Solr/Exception.php b/Apache/Solr/Exception.php
new file mode 100755
index 0000000..e6bc4f4
--- /dev/null
+++ b/Apache/Solr/Exception.php
@@ -0,0 +1,50 @@
+
+ */
+
+class Apache_Solr_Exception extends Exception
+{
+ /**
+ * SVN Revision meta data for this class
+ */
+ const SVN_REVISION = '$Revision: 54 $';
+
+ /**
+ * SVN ID meta data for this class
+ */
+ const SVN_ID = '$Id: Exception.php 54 2011-02-04 16:29:18Z donovan.jimenez $';
+}
\ No newline at end of file
diff --git a/Apache/Solr/HttpTransport/Abstract.php b/Apache/Solr/HttpTransport/Abstract.php
new file mode 100755
index 0000000..cf9f76d
--- /dev/null
+++ b/Apache/Solr/HttpTransport/Abstract.php
@@ -0,0 +1,89 @@
+, Donovan Jimenez
+ * ...
+ * $solr = new Apache_Solr_Service(); //or explicitly new Apache_Solr_Service('localhost', 8180, '/solr')
+ *
+ * if ($solr->ping())
+ * {
+ * $solr->deleteByQuery('*:*'); //deletes ALL documents - be careful :)
+ *
+ * $document = new Apache_Solr_Document();
+ * $document->id = uniqid(); //or something else suitably unique
+ *
+ * $document->title = 'Some Title';
+ * $document->content = 'Some content for this wonderful document. Blah blah blah.';
+ *
+ * $solr->addDocument($document); //if you're going to be adding documents in bulk using addDocuments
+ * //with an array of documents is faster
+ *
+ * $solr->commit(); //commit to see the deletes and the document
+ * $solr->optimize(); //merges multiple segments into one
+ *
+ * //and the one we all care about, search!
+ * //any other common or custom parameters to the request handler can go in the
+ * //optional 4th array argument.
+ * $solr->search('content:blah', 0, 10, array('sort' => 'timestamp desc'));
+ * }
+ * ...
+ *
+ *
+ * @todo Investigate using other HTTP clients other than file_get_contents built-in handler. Could provide performance
+ * improvements when dealing with multiple requests by using HTTP's keep alive functionality
+ */
+class Apache_Solr_Service
+{
+ /**
+ * SVN Revision meta data for this class
+ */
+ const SVN_REVISION = '$Revision: 59 $';
+
+ /**
+ * SVN ID meta data for this class
+ */
+ const SVN_ID = '$Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $';
+
+ /**
+ * Response writer we'll request - JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning
+ */
+ const SOLR_WRITER = 'json';
+
+ /**
+ * NamedList Treatment constants
+ */
+ const NAMED_LIST_FLAT = 'flat';
+ const NAMED_LIST_MAP = 'map';
+
+ /**
+ * Search HTTP Methods
+ */
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+
+ /**
+ * Servlet mappings
+ */
+ const PING_SERVLET = 'admin/ping';
+ const UPDATE_SERVLET = 'update';
+ const SEARCH_SERVLET = 'select';
+ const THREADS_SERVLET = 'admin/threads';
+ const EXTRACT_SERVLET = 'update/extract';
+
+ /**
+ * Server identification strings
+ *
+ * @var string
+ */
+ protected $_host, $_port, $_path;
+
+ /**
+ * Whether {@link Apache_Solr_Response} objects should create {@link Apache_Solr_Document}s in
+ * the returned parsed data
+ *
+ * @var boolean
+ */
+ protected $_createDocuments = true;
+
+ /**
+ * Whether {@link Apache_Solr_Response} objects should have multivalue fields with only a single value
+ * collapsed to appear as a single value would.
+ *
+ * @var boolean
+ */
+ protected $_collapseSingleValueArrays = true;
+
+ /**
+ * How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values
+ * are {@link Apache_Solr_Service::NAMED_LIST_MAP} (default) or {@link Apache_Solr_Service::NAMED_LIST_FLAT}.
+ *
+ * @var string
+ */
+ protected $_namedListTreatment = self::NAMED_LIST_MAP;
+
+ /**
+ * Query delimiters. Someone might want to be able to change
+ * these (to use & instead of & for example), so I've provided them.
+ *
+ * @var string
+ */
+ protected $_queryDelimiter = '?', $_queryStringDelimiter = '&', $_queryBracketsEscaped = true;
+
+ /**
+ * Constructed servlet full path URLs
+ *
+ * @var string
+ */
+ protected $_pingUrl, $_updateUrl, $_searchUrl, $_threadsUrl;
+
+ /**
+ * Keep track of whether our URLs have been constructed
+ *
+ * @var boolean
+ */
+ protected $_urlsInited = false;
+
+ /**
+ * HTTP Transport implementation (pluggable)
+ *
+ * @var Apache_Solr_HttpTransport_Interface
+ */
+ protected $_httpTransport = false;
+
+ /**
+ * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
+ *
+ * NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead
+ *
+ * @param string $value
+ * @return string
+ */
+ static public function escape($value)
+ {
+ //list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters
+ $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+
+ /**
+ * Escape a value meant to be contained in a phrase for special query characters
+ *
+ * @param string $value
+ * @return string
+ */
+ static public function escapePhrase($value)
+ {
+ $pattern = '/("|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+
+ /**
+ * Convenience function for creating phrase syntax from a value
+ *
+ * @param string $value
+ * @return string
+ */
+ static public function phrase($value)
+ {
+ return '"' . self::escapePhrase($value) . '"';
+ }
+
+ /**
+ * Constructor. All parameters are optional and will take on default values
+ * if not specified.
+ *
+ * @param string $host
+ * @param string $port
+ * @param string $path
+ * @param Apache_Solr_HttpTransport_Interface $httpTransport
+ */
+ public function __construct($host = 'localhost', $port = 8180, $path = '/solr/', $httpTransport = false)
+ {
+ $this->setHost($host);
+ $this->setPort($port);
+ $this->setPath($path);
+
+ $this->_initUrls();
+
+ if ($httpTransport)
+ {
+ $this->setHttpTransport($httpTransport);
+ }
+
+ // check that our php version is >= 5.1.3 so we can correct for http_build_query behavior later
+ $this->_queryBracketsEscaped = version_compare(phpversion(), '5.1.3', '>=');
+ }
+
+ /**
+ * Return a valid http URL given this server's host, port and path and a provided servlet name
+ *
+ * @param string $servlet
+ * @return string
+ */
+ protected function _constructUrl($servlet, $params = array())
+ {
+ if (count($params))
+ {
+ //escape all parameters appropriately for inclusion in the query string
+ $escapedParams = array();
+
+ foreach ($params as $key => $value)
+ {
+ $escapedParams[] = urlencode($key) . '=' . urlencode($value);
+ }
+
+ $queryString = $this->_queryDelimiter . implode($this->_queryStringDelimiter, $escapedParams);
+ }
+ else
+ {
+ $queryString = '';
+ }
+
+ return 'http://' . $this->_host . ':' . $this->_port . $this->_path . $servlet . $queryString;
+ }
+
+ /**
+ * Construct the Full URLs for the three servlets we reference
+ */
+ protected function _initUrls()
+ {
+ //Initialize our full servlet URLs now that we have server information
+ $this->_extractUrl = $this->_constructUrl(self::EXTRACT_SERVLET);
+ $this->_pingUrl = $this->_constructUrl(self::PING_SERVLET);
+ $this->_searchUrl = $this->_constructUrl(self::SEARCH_SERVLET);
+ $this->_threadsUrl = $this->_constructUrl(self::THREADS_SERVLET, array('wt' => self::SOLR_WRITER ));
+ $this->_updateUrl = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => self::SOLR_WRITER ));
+
+ $this->_urlsInited = true;
+ }
+
+ protected function _generateQueryString($params)
+ {
+ // use http_build_query to encode our arguments because its faster
+ // than urlencoding all the parts ourselves in a loop
+ //
+ // because http_build_query treats arrays differently than we want to, correct the query
+ // string by changing foo[#]=bar (# being an actual number) parameter strings to just
+ // multiple foo=bar strings. This regex should always work since '=' will be urlencoded
+ // anywhere else the regex isn't expecting it
+ //
+ // NOTE: before php 5.1.3 brackets were not url encoded by http_build query - we've checked
+ // the php version in the constructor and put the results in the instance variable. Also, before
+ // 5.1.2 the arg_separator parameter was not available, so don't use it
+ if ($this->_queryBracketsEscaped)
+ {
+ $queryString = http_build_query($params, null, $this->_queryStringDelimiter);
+ return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $queryString);
+ }
+ else
+ {
+ $queryString = http_build_query($params);
+ return preg_replace('/\\[(?:[0-9]|[1-9][0-9]+)\\]=/', '=', $queryString);
+ }
+ }
+
+ /**
+ * Central method for making a get operation against this Solr Server
+ *
+ * @param string $url
+ * @param float $timeout Read timeout in seconds
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
+ */
+ protected function _sendRawGet($url, $timeout = FALSE)
+ {
+ $httpTransport = $this->getHttpTransport();
+
+ $httpResponse = $httpTransport->performGetRequest($url, $timeout);
+ $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
+
+ if ($solrResponse->getHttpStatus() != 200)
+ {
+ throw new Apache_Solr_HttpTransportException($solrResponse);
+ }
+
+ return $solrResponse;
+ }
+
+ /**
+ * Central method for making a post operation against this Solr Server
+ *
+ * @param string $url
+ * @param string $rawPost
+ * @param float $timeout Read timeout in seconds
+ * @param string $contentType
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
+ */
+ protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8')
+ {
+ $httpTransport = $this->getHttpTransport();
+
+ $httpResponse = $httpTransport->performPostRequest($url, $rawPost, $contentType, $timeout);
+ $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
+
+ if ($solrResponse->getHttpStatus() != 200)
+ {
+ throw new Apache_Solr_HttpTransportException($solrResponse);
+ }
+
+ return $solrResponse;
+ }
+
+ /**
+ * Returns the set host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->_host;
+ }
+
+ /**
+ * Set the host used. If empty will fallback to constants
+ *
+ * @param string $host
+ *
+ * @throws Apache_Solr_InvalidArgumentException If the host parameter is empty
+ */
+ public function setHost($host)
+ {
+ //Use the provided host or use the default
+ if (empty($host))
+ {
+ throw new Apache_Solr_InvalidArgumentException('Host parameter is empty');
+ }
+ else
+ {
+ $this->_host = $host;
+ }
+
+ if ($this->_urlsInited)
+ {
+ $this->_initUrls();
+ }
+ }
+
+ /**
+ * Get the set port
+ *
+ * @return integer
+ */
+ public function getPort()
+ {
+ return $this->_port;
+ }
+
+ /**
+ * Set the port used. If empty will fallback to constants
+ *
+ * @param integer $port
+ *
+ * @throws Apache_Solr_InvalidArgumentException If the port parameter is empty
+ */
+ public function setPort($port)
+ {
+ //Use the provided port or use the default
+ $port = (int) $port;
+
+ if ($port <= 0)
+ {
+ throw new Apache_Solr_InvalidArgumentException('Port is not a valid port number');
+ }
+ else
+ {
+ $this->_port = $port;
+ }
+
+ if ($this->_urlsInited)
+ {
+ $this->_initUrls();
+ }
+ }
+
+ /**
+ * Get the set path.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->_path;
+ }
+
+ /**
+ * Set the path used. If empty will fallback to constants
+ *
+ * @param string $path
+ */
+ public function setPath($path)
+ {
+ $path = trim($path, '/');
+
+ $this->_path = '/' . $path . '/';
+
+ if ($this->_urlsInited)
+ {
+ $this->_initUrls();
+ }
+ }
+
+ /**
+ * Get the current configured HTTP Transport
+ *
+ * @return HttpTransportInterface
+ */
+ public function getHttpTransport()
+ {
+ // lazy load a default if one has not be set
+ if ($this->_httpTransport === false)
+ {
+ require_once(dirname(__FILE__) . '/HttpTransport/FileGetContents.php');
+
+ $this->_httpTransport = new Apache_Solr_HttpTransport_FileGetContents();
+ }
+
+ return $this->_httpTransport;
+ }
+
+ /**
+ * Set the HTTP Transport implemenation that will be used for all HTTP requests
+ *
+ * @param Apache_Solr_HttpTransport_Interface
+ */
+ public function setHttpTransport(Apache_Solr_HttpTransport_Interface $httpTransport)
+ {
+ $this->_httpTransport = $httpTransport;
+ }
+
+ /**
+ * Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will
+ * parse the response and create {@link Apache_Solr_Document} instances in place.
+ *
+ * @param boolean $createDocuments
+ */
+ public function setCreateDocuments($createDocuments)
+ {
+ $this->_createDocuments = (bool) $createDocuments;
+ }
+
+ /**
+ * Get the current state of teh create documents flag.
+ *
+ * @return boolean
+ */
+ public function getCreateDocuments()
+ {
+ return $this->_createDocuments;
+ }
+
+ /**
+ * Set the collapse single value arrays flag.
+ *
+ * @param boolean $collapseSingleValueArrays
+ */
+ public function setCollapseSingleValueArrays($collapseSingleValueArrays)
+ {
+ $this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays;
+ }
+
+ /**
+ * Get the current state of the collapse single value arrays flag.
+ *
+ * @return boolean
+ */
+ public function getCollapseSingleValueArrays()
+ {
+ return $this->_collapseSingleValueArrays;
+ }
+
+ /**
+ * Get the current default timeout setting (initially the default_socket_timeout ini setting)
+ * in seconds
+ *
+ * @return float
+ *
+ * @deprecated Use the getDefaultTimeout method on the HTTP transport implementation
+ */
+ public function getDefaultTimeout()
+ {
+ return $this->getHttpTransport()->getDefaultTimeout();
+ }
+
+ /**
+ * Set the default timeout for all calls that aren't passed a specific timeout
+ *
+ * @param float $timeout Timeout value in seconds
+ *
+ * @deprecated Use the setDefaultTimeout method on the HTTP transport implementation
+ */
+ public function setDefaultTimeout($timeout)
+ {
+ $this->getHttpTransport()->setDefaultTimeout($timeout);
+ }
+
+ /**
+ * Set how NamedLists should be formatted in the response data. This mainly effects
+ * the facet counts format.
+ *
+ * @param string $namedListTreatment
+ * @throws Apache_Solr_InvalidArgumentException If invalid option is set
+ */
+ public function setNamedListTreatment($namedListTreatment)
+ {
+ switch ((string) $namedListTreatment)
+ {
+ case Apache_Solr_Service::NAMED_LIST_FLAT:
+ $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_FLAT;
+ break;
+
+ case Apache_Solr_Service::NAMED_LIST_MAP:
+ $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_MAP;
+ break;
+
+ default:
+ throw new Apache_Solr_InvalidArgumentException('Not a valid named list treatement option');
+ }
+ }
+
+ /**
+ * Get the current setting for named list treatment.
+ *
+ * @return string
+ */
+ public function getNamedListTreatment()
+ {
+ return $this->_namedListTreatment;
+ }
+
+ /**
+ * Set the string used to separate the path form the query string.
+ * Defaulted to '?'
+ *
+ * @param string $queryDelimiter
+ */
+ public function setQueryDelimiter($queryDelimiter)
+ {
+ $this->_queryDelimiter = $queryDelimiter;
+ }
+
+ /**
+ * Set the string used to separate the parameters in thequery string
+ * Defaulted to '&'
+ *
+ * @param string $queryStringDelimiter
+ */
+ public function setQueryStringDelimiter($queryStringDelimiter)
+ {
+ $this->_queryStringDelimiter = $queryStringDelimiter;
+ }
+
+ /**
+ * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
+ * server is able to be made.
+ *
+ * @param float $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
+ * @return float Actual time taken to ping the server, FALSE if timeout or HTTP error status occurs
+ */
+ public function ping($timeout = 2)
+ {
+ $start = microtime(true);
+
+ $httpTransport = $this->getHttpTransport();
+
+ $httpResponse = $httpTransport->performHeadRequest($this->_pingUrl, $timeout);
+ $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
+
+ if ($solrResponse->getHttpStatus() == 200)
+ {
+ return microtime(true) - $start;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Call the /admin/threads servlet and retrieve information about all threads in the
+ * Solr servlet's thread group. Useful for diagnostics.
+ *
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
+ */
+ public function threads()
+ {
+ return $this->_sendRawGet($this->_threadsUrl);
+ }
+
+ /**
+ * Raw Add Method. Takes a raw post body and sends it to the update service. Post body
+ * should be a complete and well formed "add" xml document.
+ *
+ * @param string $rawPost
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
+ */
+ public function add($rawPost)
+ {
+ return $this->_sendRawPost($this->_updateUrl, $rawPost);
+ }
+
+ /**
+ * Add a Solr Document to the index
+ *
+ * @param Apache_Solr_Document $document
+ * @param boolean $allowDups
+ * @param boolean $overwritePending
+ * @param boolean $overwriteCommitted
+ * @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request.
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
+ */
+ public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0)
+ {
+ $dupValue = $allowDups ? 'true' : 'false';
+ $pendingValue = $overwritePending ? 'true' : 'false';
+ $committedValue = $overwriteCommitted ? 'true' : 'false';
+
+ $commitWithin = (int) $commitWithin;
+ $commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : '';
+
+ $rawPost = "
+ * ...
+ * $solr = new Apache_Solr_Service(); //or explicitly new Apache_Solr_Service('localhost', 8180, '/solr')
+ *
+ * if ($solr->ping())
+ * {
+ * $solr->deleteByQuery('*:*'); //deletes ALL documents - be careful :)
+ *
+ * $document = new Apache_Solr_Document();
+ * $document->id = uniqid(); //or something else suitably unique
+ *
+ * $document->title = 'Some Title';
+ * $document->content = 'Some content for this wonderful document. Blah blah blah.';
+ *
+ * $solr->addDocument($document); //if you're going to be adding documents in bulk using addDocuments
+ * //with an array of documents is faster
+ *
+ * $solr->commit(); //commit to see the deletes and the document
+ * $solr->optimize(); //merges multiple segments into one
+ *
+ * //and the one we all care about, search!
+ * //any other common or custom parameters to the request handler can go in the
+ * //optional 4th array argument.
+ * $solr->search('content:blah', 0, 10, array('sort' => 'timestamp desc'));
+ * }
+ * ...
+ *
+ *
+ * @todo Investigate using other HTTP clients other than file_get_contents built-in handler. Could provide performance
+ * improvements when dealing with multiple requests by using HTTP's keep alive functionality
+ */
+class Apache_Solr_Service
+{
+ /**
+ * SVN Revision meta data for this class
+ */
+ const SVN_REVISION = '$Revision: 59 $';
+
+ /**
+ * SVN ID meta data for this class
+ */
+ const SVN_ID = '$Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $';
+
+ /**
+ * Response writer we'll request - JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning
+ */
+ const SOLR_WRITER = 'json';
+
+ /**
+ * NamedList Treatment constants
+ */
+ const NAMED_LIST_FLAT = 'flat';
+ const NAMED_LIST_MAP = 'map';
+
+ /**
+ * Search HTTP Methods
+ */
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+
+ /**
+ * Servlet mappings
+ */
+ const PING_SERVLET = 'admin/ping';
+ const UPDATE_SERVLET = 'update';
+ const SEARCH_SERVLET = 'select';
+ const THREADS_SERVLET = 'admin/threads';
+ const EXTRACT_SERVLET = 'update/extract';
+
+ /**
+ * Server identification strings
+ *
+ * @var string
+ */
+ protected $_host, $_port, $_path;
+
+ /**
+ * Whether {@link Apache_Solr_Response} objects should create {@link Apache_Solr_Document}s in
+ * the returned parsed data
+ *
+ * @var boolean
+ */
+ protected $_createDocuments = true;
+
+ /**
+ * Whether {@link Apache_Solr_Response} objects should have multivalue fields with only a single value
+ * collapsed to appear as a single value would.
+ *
+ * @var boolean
+ */
+ protected $_collapseSingleValueArrays = true;
+
+ /**
+ * How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values
+ * are {@link Apache_Solr_Service::NAMED_LIST_MAP} (default) or {@link Apache_Solr_Service::NAMED_LIST_FLAT}.
+ *
+ * @var string
+ */
+ protected $_namedListTreatment = self::NAMED_LIST_MAP;
+
+ /**
+ * Query delimiters. Someone might want to be able to change
+ * these (to use & instead of & for example), so I've provided them.
+ *
+ * @var string
+ */
+ protected $_queryDelimiter = '?', $_queryStringDelimiter = '&', $_queryBracketsEscaped = true;
+
+ /**
+ * Constructed servlet full path URLs
+ *
+ * @var string
+ */
+ protected $_pingUrl, $_updateUrl, $_searchUrl, $_threadsUrl;
+
+ /**
+ * Keep track of whether our URLs have been constructed
+ *
+ * @var boolean
+ */
+ protected $_urlsInited = false;
+
+ /**
+ * HTTP Transport implementation (pluggable)
+ *
+ * @var Apache_Solr_HttpTransport_Interface
+ */
+ protected $_httpTransport = false;
+
+ /**
+ * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
+ *
+ * NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead
+ *
+ * @param string $value
+ * @return string
+ */
+ static public function escape($value)
+ {
+ //list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters
+ $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+
+ /**
+ * Escape a value meant to be contained in a phrase for special query characters
+ *
+ * @param string $value
+ * @return string
+ */
+ static public function escapePhrase($value)
+ {
+ $pattern = '/("|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+
+ /**
+ * Convenience function for creating phrase syntax from a value
+ *
+ * @param string $value
+ * @return string
+ */
+ static public function phrase($value)
+ {
+ return '"' . self::escapePhrase($value) . '"';
+ }
+
+ /**
+ * Constructor. All parameters are optional and will take on default values
+ * if not specified.
+ *
+ * @param string $host
+ * @param string $port
+ * @param string $path
+ * @param Apache_Solr_HttpTransport_Interface $httpTransport
+ */
+ public function __construct($host = 'localhost', $port = 8180, $path = '/solr/', $httpTransport = false)
+ {
+ $this->setHost($host);
+ $this->setPort($port);
+ $this->setPath($path);
+
+ $this->_initUrls();
+
+ if ($httpTransport)
+ {
+ $this->setHttpTransport($httpTransport);
+ }
+
+ // check that our php version is >= 5.1.3 so we can correct for http_build_query behavior later
+ $this->_queryBracketsEscaped = version_compare(phpversion(), '5.1.3', '>=');
+ }
+
+ /**
+ * Return a valid http URL given this server's host, port and path and a provided servlet name
+ *
+ * @param string $servlet
+ * @return string
+ */
+ protected function _constructUrl($servlet, $params = array())
+ {
+ if (count($params))
+ {
+ //escape all parameters appropriately for inclusion in the query string
+ $escapedParams = array();
+
+ foreach ($params as $key => $value)
+ {
+ $escapedParams[] = urlencode($key) . '=' . urlencode($value);
+ }
+
+ $queryString = $this->_queryDelimiter . implode($this->_queryStringDelimiter, $escapedParams);
+ }
+ else
+ {
+ $queryString = '';
+ }
+
+ return 'http://' . $this->_host . ':' . $this->_port . $this->_path . $servlet . $queryString;
+ }
+
+ /**
+ * Construct the Full URLs for the three servlets we reference
+ */
+ protected function _initUrls()
+ {
+ //Initialize our full servlet URLs now that we have server information
+ $this->_extractUrl = $this->_constructUrl(self::EXTRACT_SERVLET);
+ $this->_pingUrl = $this->_constructUrl(self::PING_SERVLET);
+ $this->_searchUrl = $this->_constructUrl(self::SEARCH_SERVLET);
+ $this->_threadsUrl = $this->_constructUrl(self::THREADS_SERVLET, array('wt' => self::SOLR_WRITER ));
+ $this->_updateUrl = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => self::SOLR_WRITER ));
+
+ $this->_urlsInited = true;
+ }
+
+ protected function _generateQueryString($params)
+ {
+ // use http_build_query to encode our arguments because its faster
+ // than urlencoding all the parts ourselves in a loop
+ //
+ // because http_build_query treats arrays differently than we want to, correct the query
+ // string by changing foo[#]=bar (# being an actual number) parameter strings to just
+ // multiple foo=bar strings. This regex should always work since '=' will be urlencoded
+ // anywhere else the regex isn't expecting it
+ //
+ // NOTE: before php 5.1.3 brackets were not url encoded by http_build query - we've checked
+ // the php version in the constructor and put the results in the instance variable. Also, before
+ // 5.1.2 the arg_separator parameter was not available, so don't use it
+ if ($this->_queryBracketsEscaped)
+ {
+ $queryString = http_build_query($params, null, $this->_queryStringDelimiter);
+ return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $queryString);
+ }
+ else
+ {
+ $queryString = http_build_query($params);
+ return preg_replace('/\\[(?:[0-9]|[1-9][0-9]+)\\]=/', '=', $queryString);
+ }
+ }
+
+ /**
+ * Central method for making a get operation against this Solr Server
+ *
+ * @param string $url
+ * @param float $timeout Read timeout in seconds
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
+ */
+ protected function _sendRawGet($url, $timeout = FALSE)
+ {
+ $httpTransport = $this->getHttpTransport();
+
+ $httpResponse = $httpTransport->performGetRequest($url, $timeout);
+ $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
+
+ if ($solrResponse->getHttpStatus() != 200)
+ {
+ throw new Apache_Solr_HttpTransportException($solrResponse);
+ }
+
+ return $solrResponse;
+ }
+
+ /**
+ * Central method for making a post operation against this Solr Server
+ *
+ * @param string $url
+ * @param string $rawPost
+ * @param float $timeout Read timeout in seconds
+ * @param string $contentType
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
+ */
+ protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8')
+ {
+ $httpTransport = $this->getHttpTransport();
+
+ $httpResponse = $httpTransport->performPostRequest($url, $rawPost, $contentType, $timeout);
+ $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
+
+ if ($solrResponse->getHttpStatus() != 200)
+ {
+ throw new Apache_Solr_HttpTransportException($solrResponse);
+ }
+
+ return $solrResponse;
+ }
+
+ /**
+ * Returns the set host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->_host;
+ }
+
+ /**
+ * Set the host used. If empty will fallback to constants
+ *
+ * @param string $host
+ *
+ * @throws Apache_Solr_InvalidArgumentException If the host parameter is empty
+ */
+ public function setHost($host)
+ {
+ //Use the provided host or use the default
+ if (empty($host))
+ {
+ throw new Apache_Solr_InvalidArgumentException('Host parameter is empty');
+ }
+ else
+ {
+ $this->_host = $host;
+ }
+
+ if ($this->_urlsInited)
+ {
+ $this->_initUrls();
+ }
+ }
+
+ /**
+ * Get the set port
+ *
+ * @return integer
+ */
+ public function getPort()
+ {
+ return $this->_port;
+ }
+
+ /**
+ * Set the port used. If empty will fallback to constants
+ *
+ * @param integer $port
+ *
+ * @throws Apache_Solr_InvalidArgumentException If the port parameter is empty
+ */
+ public function setPort($port)
+ {
+ //Use the provided port or use the default
+ $port = (int) $port;
+
+ if ($port <= 0)
+ {
+ throw new Apache_Solr_InvalidArgumentException('Port is not a valid port number');
+ }
+ else
+ {
+ $this->_port = $port;
+ }
+
+ if ($this->_urlsInited)
+ {
+ $this->_initUrls();
+ }
+ }
+
+ /**
+ * Get the set path.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->_path;
+ }
+
+ /**
+ * Set the path used. If empty will fallback to constants
+ *
+ * @param string $path
+ */
+ public function setPath($path)
+ {
+ $path = trim($path, '/');
+
+ $this->_path = '/' . $path . '/';
+
+ if ($this->_urlsInited)
+ {
+ $this->_initUrls();
+ }
+ }
+
+ /**
+ * Get the current configured HTTP Transport
+ *
+ * @return HttpTransportInterface
+ */
+ public function getHttpTransport()
+ {
+ // lazy load a default if one has not be set
+ if ($this->_httpTransport === false)
+ {
+ require_once(dirname(__FILE__) . '/HttpTransport/FileGetContents.php');
+
+ $this->_httpTransport = new Apache_Solr_HttpTransport_FileGetContents();
+ }
+
+ return $this->_httpTransport;
+ }
+
+ /**
+ * Set the HTTP Transport implemenation that will be used for all HTTP requests
+ *
+ * @param Apache_Solr_HttpTransport_Interface
+ */
+ public function setHttpTransport(Apache_Solr_HttpTransport_Interface $httpTransport)
+ {
+ $this->_httpTransport = $httpTransport;
+ }
+
+ /**
+ * Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will
+ * parse the response and create {@link Apache_Solr_Document} instances in place.
+ *
+ * @param boolean $createDocuments
+ */
+ public function setCreateDocuments($createDocuments)
+ {
+ $this->_createDocuments = (bool) $createDocuments;
+ }
+
+ /**
+ * Get the current state of teh create documents flag.
+ *
+ * @return boolean
+ */
+ public function getCreateDocuments()
+ {
+ return $this->_createDocuments;
+ }
+
+ /**
+ * Set the collapse single value arrays flag.
+ *
+ * @param boolean $collapseSingleValueArrays
+ */
+ public function setCollapseSingleValueArrays($collapseSingleValueArrays)
+ {
+ $this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays;
+ }
+
+ /**
+ * Get the current state of the collapse single value arrays flag.
+ *
+ * @return boolean
+ */
+ public function getCollapseSingleValueArrays()
+ {
+ return $this->_collapseSingleValueArrays;
+ }
+
+ /**
+ * Get the current default timeout setting (initially the default_socket_timeout ini setting)
+ * in seconds
+ *
+ * @return float
+ *
+ * @deprecated Use the getDefaultTimeout method on the HTTP transport implementation
+ */
+ public function getDefaultTimeout()
+ {
+ return $this->getHttpTransport()->getDefaultTimeout();
+ }
+
+ /**
+ * Set the default timeout for all calls that aren't passed a specific timeout
+ *
+ * @param float $timeout Timeout value in seconds
+ *
+ * @deprecated Use the setDefaultTimeout method on the HTTP transport implementation
+ */
+ public function setDefaultTimeout($timeout)
+ {
+ $this->getHttpTransport()->setDefaultTimeout($timeout);
+ }
+
+ /**
+ * Set how NamedLists should be formatted in the response data. This mainly effects
+ * the facet counts format.
+ *
+ * @param string $namedListTreatment
+ * @throws Apache_Solr_InvalidArgumentException If invalid option is set
+ */
+ public function setNamedListTreatment($namedListTreatment)
+ {
+ switch ((string) $namedListTreatment)
+ {
+ case Apache_Solr_Service::NAMED_LIST_FLAT:
+ $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_FLAT;
+ break;
+
+ case Apache_Solr_Service::NAMED_LIST_MAP:
+ $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_MAP;
+ break;
+
+ default:
+ throw new Apache_Solr_InvalidArgumentException('Not a valid named list treatement option');
+ }
+ }
+
+ /**
+ * Get the current setting for named list treatment.
+ *
+ * @return string
+ */
+ public function getNamedListTreatment()
+ {
+ return $this->_namedListTreatment;
+ }
+
+ /**
+ * Set the string used to separate the path form the query string.
+ * Defaulted to '?'
+ *
+ * @param string $queryDelimiter
+ */
+ public function setQueryDelimiter($queryDelimiter)
+ {
+ $this->_queryDelimiter = $queryDelimiter;
+ }
+
+ /**
+ * Set the string used to separate the parameters in thequery string
+ * Defaulted to '&'
+ *
+ * @param string $queryStringDelimiter
+ */
+ public function setQueryStringDelimiter($queryStringDelimiter)
+ {
+ $this->_queryStringDelimiter = $queryStringDelimiter;
+ }
+
+ /**
+ * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
+ * server is able to be made.
+ *
+ * @param float $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
+ * @return float Actual time taken to ping the server, FALSE if timeout or HTTP error status occurs
+ */
+ public function ping($timeout = 2)
+ {
+ $start = microtime(true);
+
+ $httpTransport = $this->getHttpTransport();
+
+ $httpResponse = $httpTransport->performHeadRequest($this->_pingUrl, $timeout);
+ $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
+
+ if ($solrResponse->getHttpStatus() == 200)
+ {
+ return microtime(true) - $start;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Call the /admin/threads servlet and retrieve information about all threads in the
+ * Solr servlet's thread group. Useful for diagnostics.
+ *
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
+ */
+ public function threads()
+ {
+ return $this->_sendRawGet($this->_threadsUrl);
+ }
+
+ /**
+ * Raw Add Method. Takes a raw post body and sends it to the update service. Post body
+ * should be a complete and well formed "add" xml document.
+ *
+ * @param string $rawPost
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
+ */
+ public function add($rawPost)
+ {
+ return $this->_sendRawPost($this->_updateUrl, $rawPost);
+ }
+
+ /**
+ * Add a Solr Document to the index
+ *
+ * @param Apache_Solr_Document $document
+ * @param boolean $allowDups
+ * @param boolean $overwritePending
+ * @param boolean $overwriteCommitted
+ * @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request.
+ * @return Apache_Solr_Response
+ *
+ * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
+ */
+ public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0)
+ {
+ $dupValue = $allowDups ? 'true' : 'false';
+ $pendingValue = $overwritePending ? 'true' : 'false';
+ $committedValue = $overwriteCommitted ? 'true' : 'false';
+
+ $commitWithin = (int) $commitWithin;
+ $commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : '';
+
+ $rawPost = "' . $response['response']->numFound . ' result(s) found
'; + if ($response['response']->numFound > 0) { + $output .= 'Book: ' . $doc->title . ' (' . $doc->author . ')
'; + $output .= 'Chapter: ' . $doc->chapter . '
'; + $output .= 'Example: ' . $doc->example . '
'; + $output .= ''; + $output .= '0 result(s) found
'; + } + } + + $commands[] = ajax_command_html("#ajax-search-result-replace", $output); + + return array('#type' => 'ajax', '#commands' => $commands); + +} -- cgit