You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

224 lines
7.5 KiB

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