1<?php 2 3/** 4 * DokuWiki Plugin extension (Helper Component) 5 * 6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7 * @author Michael Hamann <michael@content-space.de> 8 */ 9 10use dokuwiki\Cache\Cache; 11use dokuwiki\Extension\Plugin; 12use dokuwiki\Extension\PluginController; 13use dokuwiki\HTTP\DokuHTTPClient; 14 15/** 16 * Class helper_plugin_extension_repository provides access to the extension repository on dokuwiki.org 17 */ 18class helper_plugin_extension_repository extends Plugin 19{ 20 public const EXTENSION_REPOSITORY_API = 'https://www.dokuwiki.org/lib/plugins/pluginrepo/api.php'; 21 22 private $loaded_extensions = []; 23 private $has_access; 24 25 /** 26 * Initialize the repository (cache), fetches data for all installed plugins 27 */ 28 public function init() 29 { 30 /* @var PluginController $plugin_controller */ 31 global $plugin_controller; 32 if ($this->hasAccess()) { 33 $list = $plugin_controller->getList('', true); 34 $request_data = ['fmt' => 'json']; 35 $request_needed = false; 36 foreach ($list as $name) { 37 $cache = new Cache('##extension_manager##' . $name, '.repo'); 38 39 if ( 40 !isset($this->loaded_extensions[$name]) && 41 $this->hasAccess() && 42 !$cache->useCache(['age' => 3600 * 24]) 43 ) { 44 $this->loaded_extensions[$name] = true; 45 $request_data['ext'][] = $name; 46 $request_needed = true; 47 } 48 } 49 50 if ($request_needed) { 51 $httpclient = new DokuHTTPClient(); 52 $data = $httpclient->post(self::EXTENSION_REPOSITORY_API, $request_data); 53 if ($data !== false) { 54 try { 55 $extensions = json_decode($data, true, 512, JSON_THROW_ON_ERROR); 56 foreach ($extensions as $extension) { 57 $cache = new Cache('##extension_manager##' . $extension['plugin'], '.repo'); 58 $cache->storeCache(serialize($extension)); 59 } 60 } catch (JsonException $e) { 61 msg($this->getLang('repo_badresponse'), -1); 62 $this->has_access = false; 63 } 64 } else { 65 $this->has_access = false; 66 } 67 } 68 } 69 } 70 71 /** 72 * If repository access is available 73 * 74 * @param bool $usecache use cached result if still valid 75 * @return bool If repository access is available 76 */ 77 public function hasAccess($usecache = true) 78 { 79 if ($this->has_access === null) { 80 $cache = new Cache('##extension_manager###hasAccess', '.repo'); 81 82 if (!$cache->useCache(['age' => 60 * 10, 'purge' => !$usecache])) { 83 $httpclient = new DokuHTTPClient(); 84 $httpclient->timeout = 5; 85 $data = $httpclient->get(self::EXTENSION_REPOSITORY_API . '?cmd=ping'); 86 if ($data === false) { 87 $this->has_access = false; 88 $cache->storeCache(0); 89 } elseif ($data !== '1') { 90 msg($this->getLang('repo_badresponse'), -1); 91 $this->has_access = false; 92 $cache->storeCache(0); 93 } else { 94 $this->has_access = true; 95 $cache->storeCache(1); 96 } 97 } else { 98 $this->has_access = ($cache->retrieveCache(false) == 1); 99 } 100 } 101 return $this->has_access; 102 } 103 104 /** 105 * Get the remote data of an individual plugin or template 106 * 107 * @param string $name The plugin name to get the data for, template names need to be prefix by 'template:' 108 * @return array The data or null if nothing was found (possibly no repository access) 109 */ 110 public function getData($name) 111 { 112 $cache = new Cache('##extension_manager##' . $name, '.repo'); 113 114 if ( 115 !isset($this->loaded_extensions[$name]) && 116 $this->hasAccess() && 117 !$cache->useCache(['age' => 3600 * 24]) 118 ) { 119 $this->loaded_extensions[$name] = true; 120 $httpclient = new DokuHTTPClient(); 121 $data = $httpclient->get(self::EXTENSION_REPOSITORY_API . '?fmt=json&ext[]=' . urlencode($name)); 122 if ($data !== false) { 123 try { 124 $result = json_decode($data, true, 512, JSON_THROW_ON_ERROR); 125 if (count($result)) { 126 $cache->storeCache(serialize($result[0])); 127 return $result[0]; 128 } 129 } catch (JsonException $e) { 130 msg($this->getLang('repo_badresponse'), -1); 131 $this->has_access = false; 132 } 133 } else { 134 $this->has_access = false; 135 } 136 } 137 if (file_exists($cache->cache)) { 138 return unserialize($cache->retrieveCache(false)); 139 } 140 return []; 141 } 142 143 /** 144 * Search for plugins or templates using the given query string 145 * 146 * @param string $q the query string 147 * @return array a list of matching extensions 148 */ 149 public function search($q) 150 { 151 $query = $this->parseQuery($q); 152 $query['fmt'] = 'json'; 153 154 $httpclient = new DokuHTTPClient(); 155 $data = $httpclient->post(self::EXTENSION_REPOSITORY_API, $query); 156 if ($data === false) return []; 157 try { 158 $result = json_decode($data, true, 512, JSON_THROW_ON_ERROR); 159 } catch (JsonException $e) { 160 msg($this->getLang('repo_badresponse'), -1); 161 return []; 162 } 163 164 $ids = []; 165 166 // store cache info for each extension 167 foreach ($result as $ext) { 168 $name = $ext['plugin']; 169 $cache = new Cache('##extension_manager##' . $name, '.repo'); 170 $cache->storeCache(serialize($ext)); 171 $ids[] = $name; 172 } 173 174 return $ids; 175 } 176 177 /** 178 * Parses special queries from the query string 179 * 180 * @param string $q 181 * @return array 182 */ 183 protected function parseQuery($q) 184 { 185 $parameters = ['tag' => [], 'mail' => [], 'type' => [], 'ext' => []]; 186 187 // extract tags 188 if (preg_match_all('/(^|\s)(tag:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { 189 foreach ($matches as $m) { 190 $q = str_replace($m[2], '', $q); 191 $parameters['tag'][] = $m[3]; 192 } 193 } 194 // extract author ids 195 if (preg_match_all('/(^|\s)(authorid:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { 196 foreach ($matches as $m) { 197 $q = str_replace($m[2], '', $q); 198 $parameters['mail'][] = $m[3]; 199 } 200 } 201 // extract extensions 202 if (preg_match_all('/(^|\s)(ext:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { 203 foreach ($matches as $m) { 204 $q = str_replace($m[2], '', $q); 205 $parameters['ext'][] = $m[3]; 206 } 207 } 208 // extract types 209 if (preg_match_all('/(^|\s)(type:([\S]+))/', $q, $matches, PREG_SET_ORDER)) { 210 foreach ($matches as $m) { 211 $q = str_replace($m[2], '', $q); 212 $parameters['type'][] = $m[3]; 213 } 214 } 215 216 // FIXME make integer from type value 217 218 $parameters['q'] = trim($q); 219 return $parameters; 220 } 221} 222 223// vim:ts=4:sw=4:et: 224