). all credit to * Gilles Devaux () (https://github.com/flaptor/indextank-php) */ class DoofinderApi { /* * Basic client for an account. * It needs an API url to be constructed. * Its only method is to query the doofinder search server * Returns a DoofinderResults object */ const URL_SUFFIX = '-search.doofinder.com'; const DEFAULT_TIMEOUT = 10000; const DEFAULT_RPP = 10; const DEFAULT_PARAMS_PREFIX = 'dfParam_'; const DEFAULT_API_VERSION = '5'; const VERSION = '5.2.3'; private $api_key = null; // user API_KEY private $hashid = null; // hashid of the doofinder account private $apiVersion = null; private $url = null; private $results = null; private $query = null; private $search_options = array(); // assoc. array with doofinder options to be sent as request parameters private $page = 1; // the page of the search results we're at private $queryName = null; // the name of the last successfull query made private $lastQuery = null; // the last successfull query made private $total = null; // total number of results obtained private $maxScore = null; private $paramsPrefix = self::DEFAULT_PARAMS_PREFIX; private $serializationArray = null; private $queryParameter = 'query'; // the parameter used for querying private $allowedParameters = array('page', 'rpp', 'timeout', 'types', 'filter', 'query_name', 'transformer'); // request parameters that doofinder handle /** * Constructor. account's hashid and api version set here * * @param string $hashid the account's hashid * @param boolean $fromParams if set, the object is unserialized from GET or POST params * @param array $init_options. associative array with some options: * -'prefix' (default: 'dfParam_')=> the prefix to use when serializing. * -'queryParameter' (default: 'query') => the parameter used for querying * -'apiVersion' (default: '4')=> the api of the search server to query * -'restrictedRequest'(default: $_REQUEST): =>restrict request object * to look for params when unserializing. either 'get' or 'post' * @throws DoofinderException if $hashid is not a md5 hash or api is no 4, 3.0 or 1.0 */ public function __construct($hashid, $api_key, $fromParams = false, $init_options = array()) { $zone_key_array = explode('-', $api_key); if (2 === count($zone_key_array)) { $this->api_key = $zone_key_array[1]; $this->zone = $zone_key_array[0]; $this->url = "https://" . $this->zone . self::URL_SUFFIX; } else { throw new DoofinderException("API Key is no properly set."); } if (array_key_exists('prefix', $init_options)) { $this->paramsPrefix = $init_options['prefix']; } $this->allowedParameters = array_map(array($this, 'addprefix'), $this->allowedParameters); if (array_key_exists('queryParameter', $init_options)) { $this->queryParameter = $init_options['queryParameter']; } else { $this->queryParameter = $this->paramsPrefix . $this->queryParameter; } $this->apiVersion = array_key_exists('apiVersion', $init_options) ? $init_options['apiVersion'] : self::DEFAULT_API_VERSION; $this->serializationArray = $_REQUEST; if (array_key_exists('restrictedRequest', $init_options)) { switch (strtolower($init_options['restrictedRequest'])) { case 'get': $this->serializationArray = $_GET; break; case 'post': $this->serializationArray = $_POST; break; } } $patt = '/^[0-9a-f]{32}$/i'; if (!preg_match($patt, $hashid)) { throw new DoofinderException("Wrong hashid"); } if (!in_array($this->apiVersion, array('5', '4', '3.0', '1.0'))) { throw new DoofinderException('Wrong API'); } $this->hashid = $hashid; if ($fromParams) { $this->fromQuerystring(); } } private function addprefix($value) { return $this->paramsPrefix . $value; } /* * translateFilter * * translates a range filter to the new ES format * 'from'=>9, 'to'=>20 to 'gte'=>9, 'lte'=>20 * * @param array $filter * @return array the translated filter */ private function translateFilter($filter) { $new_filter = array(); foreach ($filter as $key => $value) { if ($key === 'from') { $new_filter['gte'] = $value; } elseif ($key === 'to') { $new_filter['lte'] = $value; } else { $new_filter[$key] = $value; } } return $new_filter; } private function reqHeaders() { $headers = array(); $headers[] = 'Expect:'; //Fixes the HTTP/1.1 417 Expectation Failed $authHeaderName = $this->apiVersion == '4' ? 'API Token: ' : 'authorization: '; $headers[] = $authHeaderName . $this->api_key; //API Authorization return $headers; } private function apiCall($entry_point = 'search', $params = array()) { $params['hashid'] = $this->hashid; $args = http_build_query($this->sanitize($params)); // remove any null value from the array $url = $this->url . '/' . $this->apiVersion . '/' . $entry_point . '?' . $args; $session = curl_init($url); curl_setopt($session, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($session, CURLOPT_HEADER, false); // Tell curl not to return headers curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Tell curl to return the response curl_setopt($session, CURLOPT_HTTPHEADER, $this->reqHeaders()); // Adding request headers //IF YOU MAKE REQUEST FROM LOCALHOST OR HAVE SERVER CERTIFICATE ISSUE $disableSSLVerify = Configuration::get('DF_DSBL_HTTPS_CURL'); if ($disableSSLVerify) { curl_setopt($session, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($session, CURLOPT_SSL_VERIFYPEER, 0); } $response = curl_exec($session); $httpCode = curl_getinfo($session, CURLINFO_HTTP_CODE); $debugCurlError = Configuration::get('DF_DEBUG_CURL'); if ($debugCurlError) { print curl_errno($session); } curl_close($session); if (floor($httpCode / 100) == 2) { return $response; } throw new DoofinderException($httpCode . ' - ' . $response, $httpCode); } public function getOptions() { return $this->apiCall('options/' . $this->hashid); } /** * query. makes the query to the doofinder search server. * also set several search parameters through it's $options argument * * @param string $query the search query * @param int $page the page number or the results to show * @param array $options query options: * - 'rpp'=> number of results per page. default 10 * - 'timeout' => timeout after which the search server drops the conn. * defaults to 10 seconds * - 'types' => types of index to search at. default: all. * - 'filter' => filter to apply. ['color'=>['red','blue'], 'price'=>['from'=>33]] * - any other param will be sent as a request parameter * @return DoofinderResults results */ public function query($query = null, $page = null, $options = array()) { if ($query) { $this->search_options['query'] = $query; } if ($page) { $this->search_options['page'] = (int) $page; } foreach ($options as $optionName => $optionValue) { $this->search_options[$optionName] = $options[$optionName]; } $params = $this->search_options; // translate filters if (!empty($params['filter'])) { foreach ($params['filter'] as $filterName => $filterValue) { $params['filter'][$filterName] = $this->translateFilter($filterValue); } } // no query? then match all documents if (!$this->optionExists('query') || !trim($this->search_options['query'])) { $params['query_name'] = 'match_all'; } // if filters without query_name, pre-query first to obtain it. if (empty($params['query_name']) && !empty($params['filter'])) { $filter = $params['filter']; unset($params['filter']); $dfResults = new DoofinderResults($this->apiCall('search', $params)); $params['query_name'] = $dfResults->getProperty('query_name'); $params['filter'] = $filter; } $dfResults = new DoofinderResults($this->apiCall('search', $params)); $this->page = $dfResults->getProperty('page'); $this->total = $dfResults->getProperty('total'); $this->search_options['query'] = $dfResults->getProperty('query'); $this->maxScore = $dfResults->getProperty('max_score'); $this->queryName = $dfResults->getProperty('query_name'); $this->lastQuery = $dfResults->getProperty('query'); return $dfResults; } /** * hasNext * * @return boolean true if there is another page of results */ public function hasNext() { return $this->page * $this->getRpp() < $this->total; } /** * hasPrev * * @return true if there is a previous page of results */ public function hasPrev() { return ($this->page - 1) * $this->getRpp() > 0; } /** * getPage * * obtain the current page number * @return int the page number */ public function getPage() { return $this->page; } /** * setFilter * * set a filter for the query * @param string filterName the name of the filter to set * @param array filter if simple array, terms filter assumed * if 'from', 'to' in keys, range filter assumed */ public function setFilter($filterName, $filter) { if (!$this->optionExists('filter')) { $this->search_options['filter'] = array(); } $this->search_options['filter'][$filterName] = $filter; } /** * getFilter * * get conditions for certain filter * @param string filterName * @return array filter conditions: - simple array if terms filter * - 'from', 'to' assoc array if range f. * @return false if no filter definition found */ public function getFilter($filterName) { if ($this->optionExists('filter') && isset($this->search_options['filter'][$filterName])) { return $this->filter[$filterName]; } return false; } /** * getFilters * * get all filters and their configs * @return array assoc array filterName => filterConditions */ public function getFilters() { if (isset($this->search_options['filter'])) { return $this->search_options['filter']; } else { return false; } } /** * addTerm * * add a term to a terms filter * @param string filterName the filter to add the term to * @param string term the term to add */ public function addTerm($filterName, $term) { if (!$this->optionExists('filter')) { $this->search_options['filter'] = array($filterName => array()); } if (!isset($this->search_options['filter'][$filterName])) { $this->filter[$filterName] = array(); $this->search_options['filter'][$filterName] = array(); } $this->filter[$filterName][] = $term; $this->search_options['filter'][$filterName][] = $term; } /** * removeTerm * * remove a term from a terms filter * @param string filterName the filter to remove the term from * @param string term the term to be removed */ public function removeTerm($filterName, $term) { if ($this->optionExists('filter') && isset($this->search_options['filter'][$filterName]) && in_array($term, $this->search_options['filter'][$filterName]) ) { function filter_me($value) { global $term; return $value != $term; } $this->search_options['filter'][$filterName] = array_filter($this->search_options['filter'][$filterName], 'filter_me'); } } /** * setRange * * set a range filter * @param string filterName the filter to set * @param int from the lower bound value. included * @param int to the upper bound value. included */ public function setRange($filterName, $from = null, $to = null) { if (!$this->optionExists('filter')) { $this->search_options['filter'] = array($filterName => array()); } if (!isset($this->search_options['filter'][$filterName])) { $this->search_options['filter'][$filterName] = array(); } if ($from) { $this->search_options['filter'][$filterName]['from'] = $from; } if ($to) { $this->search_options['filter'][$filterName]['to'] = $from; } } /** * toQuerystring * * 'serialize' the object's state to querystring params * @param int $page the pagenumber. defaults to the current page */ public function toQuerystring($page = null) { foreach ($this->search_options as $paramName => $paramValue) { if ($paramName == 'query') { $toParams[$this->queryParameter] = $paramValue; } else { $toParams[$this->paramsPrefix . $paramName] = $paramValue; } } if ($page) { $toParams[$this->paramsPrefix . 'page'] = $page; } return http_build_query($toParams); } /** * fromQuerystring * * obtain object's state from querystring params * @param string $params where to obtain params from: * - 'GET' $_GET params (default) * - 'POST' $_POST params */ public function fromQuerystring() { $doofinderReqParams = array_filter(array_keys($this->serializationArray), array($this, 'belongsToDoofinder')); foreach ($doofinderReqParams as $dfReqParam) { if ($dfReqParam == $this->queryParameter) { $keey = 'query'; } else { $keey = substr($dfReqParam, strlen($this->paramsPrefix)); } $this->search_options[$keey] = $this->serializationArray[$dfReqParam]; } } /** * sanitize * * Clean array of keys with empty values * * @param array $params array to be cleaned * @return array array with no empty keys */ private function sanitize($params) { $result = array(); foreach ($params as $name => $value) { if (is_array($value)) { $result[$name] = $this->sanitize($value); } elseif (trim($value)) { $result[$name] = $value; } } return $result; } /** * belongsToDoofinder * * to know if certain parameter name belongs to doofinder serialization parameters * * @param string $paramName name of the param * @return boolean true or false. */ private function belongsToDoofinder($paramName) { if ($pos = strpos($paramName, '[')) { $paramName = substr($paramName, 0, $pos); } return in_array($paramName, $this->allowedParameters) || $paramName == $this->queryParameter; } /** * optionExists * * checks whether a search option is defined in $this->search_options * * @param string $optionName * @return boolean */ private function optionExists($optionName) { return array_key_exists($optionName, $this->search_options); } /** * nextPage * * obtain the results for the next page * @return DoofinderResults if there are results. * @return null otherwise */ public function nextPage() { if ($this->hasNext()) { return $this->query($this->lastQuery, $this->page + 1); } return null; } /** * prevPage * * obtain results for the previous page * @return DoofinderResults * @return null otherwise */ public function prevPage() { if ($this->hasPrev()) { return $this->query($this->lastQuery, $this->page - 1); } return null; } /** * numPages * * @return integer the number of pages */ public function numPages() { return ceil($this->total / $this->getRpp()); } public function getRpp() { $rpp = $this->optionExists('rpp') ? $this->search_options['rpp'] : null; $rpp = $rpp ? $rpp : self::DEFAULT_RPP; return $rpp; } /** * setApiVersion * * sets the api version to use. * @param string $apiVersion the api version , '1.0' or '3.0' or '4' */ public function setApiVersion($apiVersion) { $this->apiVersion = $apiVersion; } /** * setPrefix * * sets the prefix that will be used for serialization to querystring params * @param string $prefix the prefix */ public function setPrefix($prefix) { $this->paramsPrefix = $prefix; } /** * setQueryName * * sets query_name * CAUTION: node will complain if this is wrong */ public function setQueryName($queryName) { $this->queryName = $queryName; } /** * getFilterType * obtain the filter type (i.e. 'terms' or 'numeric range' from its conditions) * @param array filter conditions * @return string 'terms' or 'numericrange' false otherwise */ private function getFilterType($filter) { if (!is_array($filter)) { return false; } if (count(array_intersect(array('from', 'to'), array_keys($filter))) > 0) { return 'numericrange'; } return 'terms'; } } include_once dirname(__FILE__) . '/DoofinderResults.php'; include_once dirname(__FILE__) . '/DoofinderException.php';