Esempio n. 1
0
 /**
  * Check spelling
  * 
  * @param QueryTerms[] $query_terms
  */
 public function checkSpelling(array $query_terms)
 {
     $registry = Registry::getInstance();
     $app_id = $registry->getConfig('BING_ID', true);
     $suggestion = new Suggestion();
     $client = Factory::getHttpClient();
     // @todo: see if we can't collapse multiple terms into a single spellcheck query
     foreach ($query_terms as $term) {
         $query = $term->phrase;
         $query = urlencode(trim($query));
         $correction = null;
         // get spell suggestion
         try {
             $url = "http://api.search.live.net/xml.aspx?Appid={$app_id}&sources=spell&query={$query}";
             $response = $client->getUrl($url);
             // process it
             $xml = Parser::convertToDOMDocument($response);
             // echo header("Content-Type: text/xml"); echo $xml->saveXML(); exit;
             $suggestion_node = $xml->getElementsByTagName('Value')->item(0);
             if ($suggestion_node != null) {
                 $correction = $suggestion_node->nodeValue;
             }
         } catch (\Exception $e) {
             trigger_error('Could not process spelling suggestion: ' . $e->getTraceAsString(), E_USER_WARNING);
         }
         // got one
         if ($correction != null) {
             $term->phrase = $suggestion_node->nodeValue;
             $suggestion->addTerm($term);
         }
     }
     return $suggestion;
 }
Esempio n. 2
0
 /**
  * Check spelling
  * 
  * @param array of QueryTerms $query_terms
  */
 public function checkSpelling(array $query_terms)
 {
     $config = Config::getInstance();
     $id = $config->getConfig("SUMMON_ID", false);
     $key = $config->getConfig("SUMMON_KEY", false);
     $suggestion = new Suggestion();
     if ($id != null && $key != null) {
         $client = new SummonClient($id, $key, Factory::getHttpClient());
         // @todo: see if we can't collapse multiple terms into a single spellcheck query
         foreach ($query_terms as $term_original) {
             $term = clone $term_original;
             $query = $term->phrase;
             $query = urlencode(trim($query));
             $correction = null;
             // get spell suggestion
             try {
                 $correction = $client->checkSpelling($query);
             } catch (\Exception $e) {
                 throw $e;
                 // @todo: remove after testing
                 trigger_error('Could not process spelling suggestion: ' . $e->getTraceAsString(), E_USER_WARNING);
             }
             // got one
             if ($correction != null) {
                 $term->phrase = $correction;
                 $suggestion->addTerm($term);
             }
         }
     }
     return $suggestion;
 }
Esempio n. 3
0
 /**
  * Create Worldcat Search Engine
  */
 public function __construct($role, $source)
 {
     parent::__construct();
     $config_key = $this->config->getConfig("WORLDCAT_API_KEY", true);
     $config_always_guest = $this->config->getConfig("WORLDCAT_SEARCH_AS_GUEST", false);
     // worldcat search object
     $this->worldcat_client = new Worldcat($config_key, Factory::getHttpClient());
     $this->group = new ConfigGroup();
     // if user is a guest, make it open, and return it pronto, since we
     // can't use the limiters below
     if ($role == "guest" || $config_always_guest != null) {
         $this->worldcat_client->setServiceLevel("default");
     } elseif ($source != "") {
         $this->group = $this->config->getWorldcatGroup($source);
         // no workset grouping, please
         if ($this->group->frbr == "false") {
             $this->worldcat_client->setWorksetGroupings(false);
         }
         // limit to certain libraries
         if ($this->group->libraries_include != null) {
             $this->worldcat_client->limitToLibraries($this->group->libraries_include);
         }
         // exclude certain libraries
         if ($this->group->libraries_exclude != null) {
             $this->worldcat_client->excludeLibraries($this->group->libraries_exclude);
         }
         // limit results to specific document types; a limit entry will
         // take presidence over any format specifically excluded
         if ($this->group->limit_material_types != null) {
             $this->worldcat_client->limitToMaterialType($this->group->limit_material_types);
         } elseif ($this->group->exclude_material_types != null) {
             $this->worldcat_client->excludeMaterialType($this->group->exclude_material_types);
         }
     }
 }
Esempio n. 4
0
 /**
  * Create new Voyager availability lookup object
  */
 public function __construct()
 {
     $this->config = Config::getInstance();
     $this->server = $this->config->getConfig('server', true);
     $this->server = rtrim($this->server, '/') . '/';
     $ignore = $this->config->getConfig('ignore_locations', false);
     $this->ignore_locations = explode(";", $ignore);
     $this->client = Factory::getHttpClient();
 }
Esempio n. 5
0
 /**
  * Constructor
  */
 public function __construct()
 {
     parent::__construct();
     $id = $this->config->getConfig("SUMMON_ID", true);
     $key = $this->config->getConfig("SUMMON_KEY", true);
     $this->summon_client = new Summon($id, $key, Factory::getHttpClient());
     // formats to exclude
     $this->formats_exclude = explode(',', $this->config->getConfig("EXCLUDE_FORMATS"));
 }
Esempio n. 6
0
 /**
  * Parses a validation response from a CAS server to see if the returning CAS request is valid
  *
  * @param string $results		xml or plain text response from cas server
  * @return bool					true if valid, false otherwise
  * @exception 					throws exception if cannot parse response or invalid version
  */
 private function isValid()
 {
     // values from the request
     $ticket = $this->request->getParam("ticket");
     // configuration settings
     $configCasValidate = $this->registry->getConfig("CAS_VALIDATE", true);
     $configCasValidate = rtrim($configCasValidate, '/');
     // figure out which type of response this is based on the service url
     $arrURL = explode("/", $configCasValidate);
     $service = array_pop($arrURL);
     // now get it!
     $url = $configCasValidate . "?ticket=" . $ticket . "&service=" . urlencode($this->validate_url);
     $client = Factory::getHttpClient();
     $req = $client->get($url);
     $req->getCurlOptions()->set(CURLOPT_SSL_VERIFYHOST, false);
     $req->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);
     $response = $req->send();
     $results = (string) $response->getBody();
     // validate is plain text
     if ($service == "validate") {
         $message_array = explode("\n", $results);
         if (count($message_array) >= 2) {
             if ($message_array[0] == "yes") {
                 return $message_array[1];
             }
         } else {
             throw new \Exception("Could not parse CAS validation response.");
         }
     } elseif ($service == "serviceValidate" || $service == "proxyValidate") {
         // these are XML based
         $xml = Parser::convertToDOMDocument($results);
         $cas_namespace = "http://www.yale.edu/tp/cas";
         $user = $xml->getElementsByTagNameNS($cas_namespace, "user")->item(0);
         $failure = $xml->getElementsByTagNameNS($cas_namespace, "authenticationFailure")->item(0);
         if ($user != null) {
             if ($user->nodeValue != "") {
                 return $user->nodeValue;
             } else {
                 throw new \Exception("CAS validation response missing username value");
             }
         } elseif ($failure != null) {
             // see if error, rather than failed authentication
             if ($failure->getAttribute("code") == "INVALID_REQUEST") {
                 throw new \Exception("Invalid request to CAS server: " . $failure->nodeValue);
             }
         } else {
             throw new \Exception("Could not parse CAS validation response.");
         }
     } else {
         throw new \Exception("Unsupported CAS version.");
     }
     // if we got this far, the request was invalid
     return false;
 }
Esempio n. 7
0
 public function __construct($command)
 {
     $config = Config::getInstance();
     $this->apiurl = $config->getConfig("apiurl");
     $this->client = Factory::getHttpClient();
     $this->client->setUri($this->apiurl . $command);
     $api_data = $this->client->send()->getBody();
     $api_data = json_decode($api_data, true);
     $api_data = array_pop($api_data);
     $i = 1;
     foreach ($api_data as $k => $v) {
         $api_data[$k]['position'] = $i++;
     }
     $this->api_data = $api_data;
 }
Esempio n. 8
0
 public function getTotal(Query $query)
 {
     $total = 0;
     $url = $this->getUrl($query);
     $client = Factory::getHttpClient();
     $response = $client->getUrl($url);
     // extract the total number of hits in the results page;
     $arrMatches = array();
     if (preg_match('/\\(1-[0-9]{1,2} of ([0-9]{1,10})\\)/', $response, $arrMatches) != 0) {
         $total = (int) $arrMatches[1];
     } elseif (!stristr($response, "No matches found") && !stristr($response, "NO ENTRIES FOUND")) {
         $total = 1;
         // only found one response, catalog jumped right to full display
     }
     return $total;
 }
Esempio n. 9
0
 /**
  * Constructor
  * 
  */
 public function __construct($type, $targets)
 {
     // full set of Targets from Search25 API
     $this->config = Config::getInstance();
     $url = $this->config->getConfig("apiurl");
     $command = '/institutions.json?active=true';
     $this->client = Factory::getHttpClient();
     $this->client->setUri($url . $command);
     $api_institutions = $this->client->send()->getBody();
     if ($type == null) {
         $command = "/z3950server.json?active=true";
     } else {
         $command = "/z3950server.json?active=true&source_type={$type}";
     }
     $this->client->setUri($url . $command);
     $api_targets = $this->client->send()->getBody();
     $api_institutions = json_decode($api_institutions, true);
     $api_institutions = array_pop($api_institutions);
     $api_targets = json_decode($api_targets, true);
     $api_targets = array_pop($api_targets);
     $tgtArray = array();
     foreach ($api_targets as $api_target) {
         $data = array();
         $key = $api_target['m25_code'];
         $data['pz2_key'] = $key;
         $data['z3950_location'] = $api_target['z39_name'];
         $data['linkback_url'] = $api_target['linkback_url'];
         $i = 0;
         while ($api_institutions[$i]['m25_code'] != $key) {
             $i++;
         }
         $data['short_name'] = $api_institutions[$i]['short_name'];
         $data['display_name'] = htmlentities($api_institutions[$i]['full_name']);
         $data['sort_name'] = htmlentities($api_institutions[$i]['sort_name']);
         $data['library_url'] = $api_institutions[$i]['library_url'];
         $data['domain'] = $api_institutions[$i]['domain'];
         $target = new Target();
         $target->load($data);
         $tgtArray[] = $target;
     }
     usort($tgtArray, array($this, 'alphasort'));
     for ($i = 0; $i < count($tgtArray); $i++) {
         $tgt = $tgtArray[$i];
         $tgt->position = $i + 1;
         $this->targets[$tgt->pz2_key] = $tgt;
     }
 }
Esempio n. 10
0
 /**
  * Do the actual search and return results
  *
  * @param Query $search  search object
  * @param int $start     [optional] starting record number
  * @param int $max       [optional] max records
  * @param string $sort   [optional] sort order
  * @param bool $facets   [optional] whether to include facets
  *
  * @return Results
  */
 protected function doSearch(Search\Query $search, $start = 1, $max = 10, $sort = "", $facets = true)
 {
     $results = new Search\ResultSet($this->config);
     $query = $search->toQuery();
     $this->url = $this->server . "/search?q=" . urlencode($query);
     if ($this->config->getConfig('client')) {
         $this->url .= '&client=' . urlencode($this->config->getConfig('client'));
     }
     if ($this->config->getConfig('site')) {
         $this->url .= '&site=' . urlencode($this->config->getConfig('site'));
     }
     $this->url .= '&output=xml';
     // google results are 0-based
     if ($start != null) {
         $start = $start - 1;
         $this->url .= '&start=' . $start;
     }
     // echo $this->url; exit;
     $client = Factory::getHttpClient();
     $google_results = $client->getUrl($this->url, 3);
     $xml = simplexml_load_string($google_results);
     // header("Content-type: text/xml"); echo $xml->saveXML(); exit;
     $x = 0;
     // exact matches
     foreach ($xml->GM as $gm) {
         $record = new Record();
         $record->loadXML($gm);
         $results->addRecord($record);
     }
     // regular results
     $results_array = $xml->xpath("//RES");
     if (count($results_array) > 0 && $results_array !== false) {
         $results_xml = $results_array[0];
         $results->total = (int) $results_xml->M;
         foreach ($results_xml->R as $result_xml) {
             if ($x >= $max) {
                 break;
             }
             $record = new Record();
             $record->loadXML($result_xml);
             $results->addRecord($record);
             $x++;
         }
     }
     return $results;
 }
Esempio n. 11
0
 /**
  * Create new Innopac availability lookup object
  *
  * @param string $server		server address
  */
 public function __construct()
 {
     $this->config = Config::getInstance();
     $this->server = $this->config->getConfig('server', true);
     $this->server = rtrim($this->server, '/');
     $this->innreach = $this->config->getConfig('innreach', false, false);
     $this->convert_to_utf8 = $this->config->getConfig('convert_to_utf8', false, false);
     $availability_status = explode(';', $this->config->getConfig('available_statuses', true));
     foreach ($availability_status as $status) {
         $this->availability_status[] = trim($status);
     }
     $ignore_locations = explode(';', $this->config->getConfig('ignore_locations', true));
     foreach ($ignore_locations as $location) {
         $this->locations_to_ignore[] = trim($location);
     }
     $this->client = Factory::getHttpClient();
 }
Esempio n. 12
0
 /**
  * Constructor
  * 
  */
 public function __construct($pz2_key)
 {
     // full set of Libraries for this institution from Search25 API
     $this->config = Config::getInstance();
     $url = $this->config->getConfig("apiurl");
     $command = "/institutions/{$pz2_key}/libraries.json?active=true";
     $this->client = Factory::getHttpClient();
     $this->client->setUri($url . $command);
     $api_libraries = $this->client->send()->getBody();
     $api_libraries = json_decode($api_libraries, true);
     $api_libraries = array_pop($api_libraries);
     $arr = array();
     foreach ($api_libraries as $api_library) {
         $library = new Library();
         $api_library['pz2_key'] = $pz2_key;
         $library->load($api_library);
         $arr[] = $library;
     }
     usort($arr, array($this, 'alphasort'));
     $this->libraries = $arr;
 }
Esempio n. 13
0
 public function getRecommendations(Xerxes\Record $xerxes_record, $min_relevance = 0, $max_records = 10)
 {
     $bx_records = array();
     // now get the open url
     $open_url = $xerxes_record->getOpenURL(null, $this->sid);
     $open_url = str_replace('genre=unknown', 'genre=article', $open_url);
     // send it to bx service
     $url = $this->url . "/recommender/openurl?token=" . $this->token . "&{$open_url}" . "&res_dat=source=global&threshold={$min_relevance}&maxRecords={$max_records}";
     try {
         $client = Factory::getHttpClient();
         $client->setUri($url);
         $client->setConfig(array('timeout' => 4));
         $xml = $client->send()->getBody();
         if ($xml == "") {
             throw new \Exception("No response from bx service");
         }
     } catch (\Exception $e) {
         // just issue the exception as a warning
         trigger_error("Could not get result from bX service: " . $e->getTraceAsString(), E_USER_WARNING);
         return $bx_records;
     }
     // header("Content-type: text/xml"); echo $xml; exit;
     $doc = new \DOMDocument();
     $doc->recover = true;
     $doc->loadXML($xml);
     $xpath = new \DOMXPath($doc);
     $xpath->registerNamespace("ctx", "info:ofi/fmt:xml:xsd:ctx");
     $records = $xpath->query("//ctx:context-object");
     foreach ($records as $record) {
         $bx_record = new Record();
         $bx_record->loadXML($record);
         array_push($bx_records, $bx_record);
     }
     if (count($bx_records) > 0) {
         // first one is the record we want to find recommendations for
         // so skip it; any others are actual recommendations
         array_shift($bx_records);
     }
     return $bx_records;
 }
Esempio n. 14
0
 /**
  * Constructor
  * 
  */
 public function __construct()
 {
     // full set of Subjects from API
     $this->config = Config::getInstance();
     $this->url = $this->config->getConfig("apiurl");
     $command = '/subjects.json?active=true';
     $this->client = Factory::getHttpClient();
     $this->client->setUri($this->url . $command);
     $api_subjects = $this->client->send()->getBody();
     $api_subjects = json_decode($api_subjects, true);
     $api_subjects = array_pop($api_subjects);
     $subjArray = array();
     foreach ($api_subjects as $sub) {
         $subject = new Subject();
         $subject->load(array('name' => $sub['subject'], 'id' => $sub['subject_id'], 'position' => $sub['subject_id'], 'url' => $sub['ukat_url']));
         $subjArray[] = $subject;
     }
     usort($subjArray, array($this, 'alphasort'));
     for ($i = 0; $i < count($subjArray); $i++) {
         $subj = $subjArray[$i];
         $subj->setPosition($i);
         $this->subjects[] = $subj;
     }
 }
Esempio n. 15
0
 /**
  * Metalib Client
  *
  * Static here so we maintain the session id
  */
 public static function getMetalibClient()
 {
     if (!self::$client instanceof Metalib) {
         $config = Config::getInstance();
         $address = $config->getConfig("METALIB_ADDRESS", true);
         $username = $config->getConfig("METALIB_USERNAME", true);
         $password = $config->getConfig("METALIB_PASSWORD", true);
         self::$client = new Metalib($address, $username, $password, Factory::getHttpClient());
     }
     return self::$client;
 }
Esempio n. 16
0
 /**
  * Fetch list of databases
  * 
  * @param bool $force_new  get data from ebsco 
  * @return array
  */
 public function getDatabases($force_new = false)
 {
     $cache = new Cache();
     $id = 'ebsco_databases';
     if ($force_new == false) {
         // do we have it already?
         if (count($this->databases) > 0) {
             return $this->databases;
         }
         // check the cache
         $this->databases = $cache->get($id);
         if ($this->databases != null) {
             return $this->databases;
         }
     }
     // fetch 'em from ebsco
     $url = $this->host . '/Info?' . 'prof=' . $this->username . '&pwd=' . $this->password;
     $client = Factory::getHttpClient();
     $response = $client->getUrl($url);
     $xml = new \DOMDocument();
     $loaded = $xml->loadXML($response);
     if ($loaded == true) {
         $nodes = $xml->getElementsByTagName('db');
         if ($nodes->length > 1) {
             foreach ($nodes as $db) {
                 if ((string) $db->getAttribute('dbType') == 'Regular') {
                     $id = (string) $db->getAttribute('shortName');
                     $name = (string) $db->getAttribute('longName');
                     $this->databases[$id] = $name;
                 }
             }
             // cache 'em
             $cache->set($id, $this->databases);
         }
     }
     return $this->databases;
 }
Esempio n. 17
0
 /**
  * Do the actual search
  * 
  * @param mixed $search							search object or string
  * @param string $database						[optional] database id
  * @param int $start							[optional] starting record number
  * @param int $max								[optional] max records
  * @param string $sort							[optional] sort order
  * 
  * @return Results
  */
 protected function doSearch($search, $database, $start, $max, $sort = "relevance")
 {
     // default for sort
     if ($sort == "") {
         $sort = "relevance";
     }
     // prepare the query
     $query = "";
     if ($search instanceof Search\Query) {
         $query = $search->toQuery();
     } else {
         $query = $search;
     }
     // databases
     $databases = array();
     // we asked for this database specifically
     if ($database != "") {
         $databases = array($database);
     } else {
         // see if any supplied as facet limit
         foreach ($search->getLimits(true) as $limit) {
             array_push($databases, $limit->value);
         }
         // nope
         if (count($databases) == 0) {
             // get 'em from config
             $databases_xml = $this->config->getConfig("EBSCO_DATABASES");
             if ($databases_xml == "") {
                 throw new \Exception("No databases defined");
             }
             foreach ($databases_xml->database as $database) {
                 array_push($databases, (string) $database["id"]);
             }
         }
     }
     // construct url
     $this->url = "http://eit.ebscohost.com/Services/SearchService.asmx/Search?" . "prof=" . $this->username . "&pwd=" . $this->password . "&authType=&ipprof=" . "&query=" . urlencode($query) . "&startrec={$start}&numrec={$max}" . "&sort={$sort}" . "&format=detailed";
     // add in the databases
     foreach ($databases as $database) {
         $this->url .= "&db={$database}";
     }
     // get the xml from ebsco
     $client = Factory::getHttpClient();
     $client->setUri($this->url);
     $response = $client->send()->getBody();
     // testing
     // echo "<pre>$this->url<hr>$response</pre>"; exit;
     if ($response == null) {
         throw new \Exception("Could not connect to Ebsco search server");
     }
     // load it in
     $xml = new \DOMDocument();
     $xml->recover = true;
     $xml->loadXML($response);
     // result set
     $results = new Search\ResultSet($this->config);
     // get total
     $total = 0;
     $hits = $xml->getElementsByTagName("Hits")->item(0);
     if ($hits != null) {
         $total = (int) $hits->nodeValue;
     }
     ### hacks until ebsco gives us proper hit counts, they are almost there
     $check = 0;
     foreach ($xml->getElementsByTagName("rec") as $hits) {
         $check++;
     }
     // no hits, but we're above the first page, so the user has likely
     // skipped here, need to increment down until we find the true ceiling
     if ($check == 0 && $start > $max) {
         // but let's not get crazy here
         if ($this->deincrementing <= 8) {
             $this->deincrementing++;
             $new_start = $start - $max;
             return $this->doSearch($query, $databases, $new_start, $max, $sort);
         }
     }
     // we've reached the end prematurely, so set this to the end
     $check_end = $start + $check;
     if ($check < $max) {
         if ($check_end < $total) {
             $total = $check_end;
         }
     }
     ## end hacks
     // set total
     $results->total = $total;
     // add records
     foreach ($this->extractRecords($xml) as $record) {
         $results->addRecord($record);
     }
     // add clusters
     $facets = $this->extractFacets($xml);
     $results->setFacets($facets);
     return $results;
 }
Esempio n. 18
0
 /**
  * Do the actual fetch of an individual record
  *
  * @param string	record identifier
  * @return ResultSet
  */
 protected function doGetRecord($id)
 {
     $query = $this->getQuery();
     $request = $query->getRecordUrl($id);
     $client = Factory::getHttpClient();
     $response = $client->getUrl($request->url, null, $request->headers);
     return $this->parseResponse($response);
 }
Esempio n. 19
0
 /**
  * Establish a new session with EDS
  *
  * @param string $profile
  * @return string
  */
 public function createSession($profile)
 {
     $url = $this->server . 'createsession?profile=' . urlencode($profile);
     $client = Factory::getHttpClient();
     $xml = $client->getUrl($url, 10);
     $dom = new \DOMDocument();
     $dom->loadXML($xml);
     // header('Content-type: text/xml'); echo $dom->saveXML(); exit;
     $session_id = $dom->getElementsByTagName('SessionToken')->item(0)->nodeValue;
     return $session_id;
 }
Esempio n. 20
0
 /**
  * Do the actual search
  * 
  * @param mixed $search							string or Query, the search query
  * @param int $start							[optional] starting record number
  * @param int $max								[optional] max records
  * @param string $sort							[optional] sort order
  * 
  * @return Results
  */
 protected function doSearch($search, $start = 1, $max = 10, $sort = "")
 {
     // parse the query
     $query = "";
     if ($search instanceof Search\Query) {
         foreach ($search->getQueryTerms() as $term) {
             $query .= "&query=" . $term->field_internal . ",contains," . urlencode($term->phrase);
         }
         foreach ($search->getLimits(true) as $limit) {
             $query .= "&query=facet_" . $limit->field . ",exact," . urlencode($limit->value);
         }
     } else {
         $query = "&query=" . urlencode($search);
     }
     // on campus as string
     $on_campus = "true";
     if ($this->on_campus == false) {
         $on_campus = "false";
     }
     // create the url
     $this->url = $this->server . "/xservice/search/brief?" . "institution=" . $this->institution . "&onCampus=" . $on_campus . $query . "&indx={$start}" . "&bulkSize={$max}";
     if ($this->vid != "") {
         $this->url .= "&vid=" . $this->vid;
     }
     foreach ($this->loc as $loc) {
         $this->url .= "&loc=" . $loc;
     }
     if ($sort != "") {
         $this->url .= "&sortField={$sort}";
     }
     // get the response
     $client = Factory::getHttpClient();
     $client->setUri($this->url);
     $response = $client->send()->getBody();
     // echo $response;
     if ($response == "") {
         throw new \Exception("Could not connect to Primo server");
     }
     // load it
     $xml = Parser::convertToDOMDocument($response);
     // parse it
     return $this->parseResponse($xml);
 }
Esempio n. 21
0
 public function __construct()
 {
     $config = Config::getInstance();
     $this->apiurl = $config->getConfig("apiurl");
     $this->client = Factory::getHttpClient();
 }
Esempio n. 22
0
 /**
  * Do the actual search
  * 
  * @param string|Query $search					search object or string
  * @param int $start							[optional] starting record number
  * @param int $max								[optional] max records
  * @param string $sort							[optional] sort order
  * @param bool $include_facets					[optional] whether to include facets or not
  * 
  * @return string
  */
 protected function doSearch($search, $start, $max = 10, $sort = null, $include_facets = true)
 {
     // start
     if ($start > 0) {
         $start--;
         // solr is 0-based
     }
     ### parse the query
     $query = "";
     // passed in a query object, so handle this
     if ($search instanceof Search\Query) {
         $query = $search->toQuery();
     } else {
         $query = "&q=" . urlencode($search);
     }
     ### now the url
     $this->url = $this->server . $query;
     $this->url .= "&start={$start}&rows=" . $max . "&sort=" . urlencode($sort);
     if ($include_facets == true) {
         $this->url .= "&facet=true&facet.mincount=1";
         foreach ($this->config->getFacets() as $facet => $attributes) {
             $sort = (string) $attributes["sort"];
             $max = (string) $attributes["max"];
             $this->url .= "&facet.field={$facet}";
             if ($sort != "") {
                 $this->url .= "&f.{$facet}.facet.sort={$sort}";
             }
             if ($max != "") {
                 $this->url .= "&f.{$facet}.facet.limit={$max}";
             }
         }
     }
     // make sure we get the score
     $this->url .= "&fl=*+score";
     ## get and parse the response
     // get the data
     $client = Factory::getHttpClient();
     $client->setUri($this->url);
     $response = $client->send()->getBody();
     $xml = simplexml_load_string($response);
     if ($response == null || $xml === false) {
         throw new \Exception("Could not connect to search engine.");
     }
     // parse the results
     $results = new Search\ResultSet($this->config);
     // extract total
     $results->total = (int) $xml->result["numFound"];
     // extract records
     foreach ($this->extractRecords($xml) as $record) {
         $results->addRecord($record);
     }
     // extract facets
     $results->setFacets($this->extractFacets($xml));
     return $results;
 }