hasAccess !== null) { return $this->hasAccess; // we already checked } // check for SSL support if (!in_array('ssl', stream_get_transports())) { throw new Exception('nossl'); } // ping the API $httpclient = new DokuHTTPClient(); $httpclient->timeout = 5; $data = $httpclient->get(self::EXTENSION_REPOSITORY_API . '?cmd=ping'); if ($data === false) { $this->hasAccess = false; throw new Exception('repo_error'); } elseif ($data !== '1') { $this->hasAccess = false; throw new Exception('repo_badresponse'); } else { $this->hasAccess = true; } return $this->hasAccess; } /** * Fetch the data for multiple extensions from the repository * * @param string[] $ids A list of extension ids * @throws Exception */ protected function fetchExtensions($ids) { if (!$this->checkAccess()) return; $httpclient = new DokuHTTPClient(); $data = [ 'fmt' => 'json', 'ext' => $ids ]; $response = $httpclient->post(self::EXTENSION_REPOSITORY_API, $data); if ($response === false) { $this->hasAccess = false; throw new Exception('repo_error'); } try { $found = []; $extensions = json_decode($response, true, 512, JSON_THROW_ON_ERROR); foreach ($extensions as $extension) { $this->storeCache($extension['plugin'], $extension); $found[] = $extension['plugin']; } // extensions that have not been returned are not in the repository, but we should cache that too foreach (array_diff($ids, $found) as $id) { $this->storeCache($id, []); } } catch (JsonException $e) { $this->hasAccess = false; throw new Exception('repo_badresponse', 0, $e); } } /** * This creates a list of Extension objects from the given list of ids * * The extensions are initialized by fetching their data from the cache or the repository. * This is the recommended way to initialize a whole bunch of extensions at once as it will only do * a single API request for all extensions that are not in the cache. * * Extensions that are not found in the cache or the repository will be initialized as null. * * @param string[] $ids * @return (Extension|null)[] [id => Extension|null, ...] * @throws Exception */ public function initExtensions($ids) { $result = []; $toload = []; // first get all that are cached foreach ($ids as $id) { $data = $this->retrieveCache($id); if ($data === null) { $toload[] = $id; } else { $result[$id] = Extension::createFromRemoteData($data); } } // then fetch the rest at once if ($toload) { $this->fetchExtensions($toload); foreach ($toload as $id) { $data = $this->retrieveCache($id); if ($data === null) { $result[$id] = null; } else { $result[$id] = Extension::createFromRemoteData($data); } } } return $result; } /** * Initialize a new Extension object from remote data for the given id * * @param string $id * @return Extension|null * @throws Exception */ public function initExtension($id) { $result = $this->initExtensions([$id]); return $result[$id]; } /** * Get the pure API data for a single extension * * Used when lazy loading remote data in Extension * * @param string $id * @return array|null * @throws Exception */ public function getExtensionData($id) { $data = $this->retrieveCache($id); if ($data === null) { $this->fetchExtensions([$id]); $data = $this->retrieveCache($id); } return $data; } /** * Search for extensions using the given query string * * @param string $q the query string * @return Extension[] a list of matching extensions * @throws Exception */ public function searchExtensions($q) { if (!$this->checkAccess()) return []; $query = $this->parseQuery($q); $query['fmt'] = 'json'; $httpclient = new DokuHTTPClient(); $response = $httpclient->post(self::EXTENSION_REPOSITORY_API, $query); if ($response === false) { $this->hasAccess = false; throw new Exception('repo_error'); } try { $items = json_decode($response, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { $this->hasAccess = false; throw new Exception('repo_badresponse', 0, $e); } $results = []; foreach ($items as $item) { $this->storeCache($item['plugin'], $item); $results[] = Extension::createFromRemoteData($item); } return $results; } /** * Parses special queries from the query string * * @param string $q * @return array */ protected function parseQuery($q) { $parameters = [ 'tag' => [], 'mail' => [], 'type' => [], 'ext' => [] ]; // extract tags if (preg_match_all('/(^|\s)(tag:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['tag'][] = $m[3]; } } // extract author ids if (preg_match_all('/(^|\s)(authorid:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['mail'][] = $m[3]; } } // extract extensions if (preg_match_all('/(^|\s)(ext:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['ext'][] = $m[3]; } } // extract types if (preg_match_all('/(^|\s)(type:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $q = str_replace($m[2], '', $q); $parameters['type'][] = $m[3]; } } // FIXME make integer from type value $parameters['q'] = trim($q); return $parameters; } /** * Store the data for a single extension in the cache * * @param string $id * @param array $data */ protected function storeCache($id, $data) { $cache = new Cache(self::CACHE_PREFIX . $id, self::CACHE_SUFFIX); $cache->storeCache(serialize($data)); } /** * Retrieve the data for a single extension from the cache * * @param string $id * @return array|null the data or null if not in cache */ protected function retrieveCache($id) { $cache = new Cache(self::CACHE_PREFIX . $id, self::CACHE_SUFFIX); if ($cache->useCache(['age' => self::CACHE_TIME])) { return unserialize($cache->retrieveCache(false)); } return null; } }