1cf2dcf1bSAndreas Gohr<?php 2cf2dcf1bSAndreas Gohr 3cf2dcf1bSAndreas Gohrnamespace dokuwiki\plugin\extension; 4cf2dcf1bSAndreas Gohr 5cf2dcf1bSAndreas Gohruse dokuwiki\Extension\PluginController; 6cf2dcf1bSAndreas Gohruse dokuwiki\Utf8\PhpString; 7cf2dcf1bSAndreas Gohruse RuntimeException; 8cf2dcf1bSAndreas Gohr 9cf2dcf1bSAndreas Gohrclass Extension 10cf2dcf1bSAndreas Gohr{ 11cf2dcf1bSAndreas Gohr const TYPE_PLUGIN = 'plugin'; 12cf2dcf1bSAndreas Gohr const TYPE_TEMPLATE = 'template'; 13cf2dcf1bSAndreas Gohr 14cf2dcf1bSAndreas Gohr /** @var string "plugin"|"template" */ 15cf2dcf1bSAndreas Gohr protected string $type = self::TYPE_PLUGIN; 16cf2dcf1bSAndreas Gohr 17cf2dcf1bSAndreas Gohr /** @var string The base name of this extension */ 18cf2dcf1bSAndreas Gohr protected string $base; 19cf2dcf1bSAndreas Gohr 20cf2dcf1bSAndreas Gohr /** @var string|null The current location of this extension */ 21cf2dcf1bSAndreas Gohr protected ?string $currentDir; 22cf2dcf1bSAndreas Gohr 23cf2dcf1bSAndreas Gohr /** @var array The local info array of the extension */ 24cf2dcf1bSAndreas Gohr protected array $localInfo = []; 25cf2dcf1bSAndreas Gohr 26cf2dcf1bSAndreas Gohr /** @var array The remote info array of the extension */ 27cf2dcf1bSAndreas Gohr protected array $remoteInfo = []; 28cf2dcf1bSAndreas Gohr 29*7c9966a5SAndreas Gohr /** @var Manager|null The manager for this extension */ 30*7c9966a5SAndreas Gohr protected ?Manager $manager; 31cf2dcf1bSAndreas Gohr 32cf2dcf1bSAndreas Gohr // region Constructors 33cf2dcf1bSAndreas Gohr 34cf2dcf1bSAndreas Gohr /** 35cf2dcf1bSAndreas Gohr * The main constructor is private to force the use of the factory methods 36cf2dcf1bSAndreas Gohr */ 37cf2dcf1bSAndreas Gohr protected function __construct() 38cf2dcf1bSAndreas Gohr { 39cf2dcf1bSAndreas Gohr } 40cf2dcf1bSAndreas Gohr 41cf2dcf1bSAndreas Gohr /** 42a1e045f7SAndreas Gohr * Initializes an extension from an id 43a1e045f7SAndreas Gohr * 44a1e045f7SAndreas Gohr * @param string $id The id of the extension 45a1e045f7SAndreas Gohr * @return Extension 46a1e045f7SAndreas Gohr */ 47a1e045f7SAndreas Gohr public static function createFromId($id) 48a1e045f7SAndreas Gohr { 49a1e045f7SAndreas Gohr $extension = new self(); 50a1e045f7SAndreas Gohr $extension->initFromId($id); 51a1e045f7SAndreas Gohr return $extension; 52a1e045f7SAndreas Gohr } 53a1e045f7SAndreas Gohr 54a1e045f7SAndreas Gohr protected function initFromId($id) 55a1e045f7SAndreas Gohr { 56a1e045f7SAndreas Gohr [$type, $base] = $this->idToTypeBase($id); 57a1e045f7SAndreas Gohr $this->type = $type; 58a1e045f7SAndreas Gohr $this->base = $base; 59a1e045f7SAndreas Gohr $this->readLocalInfo(); 60a1e045f7SAndreas Gohr } 61a1e045f7SAndreas Gohr 62a1e045f7SAndreas Gohr /** 63cf2dcf1bSAndreas Gohr * Initializes an extension from a directory 64cf2dcf1bSAndreas Gohr * 65cf2dcf1bSAndreas Gohr * The given directory might be the one where the extension has already been installed to 66cf2dcf1bSAndreas Gohr * or it might be the extracted source in some temporary directory. 67cf2dcf1bSAndreas Gohr * 68cf2dcf1bSAndreas Gohr * @param string $dir Where the extension code is currently located 69cf2dcf1bSAndreas Gohr * @param string|null $type TYPE_PLUGIN|TYPE_TEMPLATE, null for auto-detection 70cf2dcf1bSAndreas Gohr * @param string $base The base name of the extension, null for auto-detection 71cf2dcf1bSAndreas Gohr * @return Extension 72cf2dcf1bSAndreas Gohr */ 73cf2dcf1bSAndreas Gohr public static function createFromDirectory($dir, $type = null, $base = null) 74cf2dcf1bSAndreas Gohr { 75cf2dcf1bSAndreas Gohr $extension = new self(); 76cf2dcf1bSAndreas Gohr $extension->initFromDirectory($dir, $type, $base); 77cf2dcf1bSAndreas Gohr return $extension; 78cf2dcf1bSAndreas Gohr } 79cf2dcf1bSAndreas Gohr 80cf2dcf1bSAndreas Gohr protected function initFromDirectory($dir, $type = null, $base = null) 81cf2dcf1bSAndreas Gohr { 82cf2dcf1bSAndreas Gohr if (!is_dir($dir)) throw new RuntimeException('Directory not found: ' . $dir); 83cf2dcf1bSAndreas Gohr $this->currentDir = realpath($dir); 84cf2dcf1bSAndreas Gohr 85cf2dcf1bSAndreas Gohr if ($type === null || $type === self::TYPE_TEMPLATE) { 86cf2dcf1bSAndreas Gohr if ( 87cf2dcf1bSAndreas Gohr file_exists($dir . '/template.info.php') || 88cf2dcf1bSAndreas Gohr file_exists($dir . '/style.ini') || 89cf2dcf1bSAndreas Gohr file_exists($dir . '/main.php') || 90cf2dcf1bSAndreas Gohr file_exists($dir . '/detail.php') || 91cf2dcf1bSAndreas Gohr file_exists($dir . '/mediamanager.php') 92cf2dcf1bSAndreas Gohr ) { 93cf2dcf1bSAndreas Gohr $this->type = self::TYPE_TEMPLATE; 94cf2dcf1bSAndreas Gohr } 95cf2dcf1bSAndreas Gohr } else { 96cf2dcf1bSAndreas Gohr $this->type = self::TYPE_PLUGIN; 97cf2dcf1bSAndreas Gohr } 98cf2dcf1bSAndreas Gohr 99cf2dcf1bSAndreas Gohr $this->readLocalInfo(); 100cf2dcf1bSAndreas Gohr 101cf2dcf1bSAndreas Gohr if ($base !== null) { 102cf2dcf1bSAndreas Gohr $this->base = $base; 103cf2dcf1bSAndreas Gohr } elseif (isset($this->localInfo['base'])) { 104cf2dcf1bSAndreas Gohr $this->base = $this->localInfo['base']; 105cf2dcf1bSAndreas Gohr } else { 106cf2dcf1bSAndreas Gohr $this->base = basename($dir); 107cf2dcf1bSAndreas Gohr } 108cf2dcf1bSAndreas Gohr } 109cf2dcf1bSAndreas Gohr 110cf2dcf1bSAndreas Gohr /** 111cf2dcf1bSAndreas Gohr * Initializes an extension from remote data 112cf2dcf1bSAndreas Gohr * 113cf2dcf1bSAndreas Gohr * @param array $data The data as returned by the repository api 114cf2dcf1bSAndreas Gohr * @return Extension 115cf2dcf1bSAndreas Gohr */ 116cf2dcf1bSAndreas Gohr public static function createFromRemoteData($data) 117cf2dcf1bSAndreas Gohr { 118cf2dcf1bSAndreas Gohr $extension = new self(); 119cf2dcf1bSAndreas Gohr $extension->initFromRemoteData($data); 120cf2dcf1bSAndreas Gohr return $extension; 121cf2dcf1bSAndreas Gohr } 122cf2dcf1bSAndreas Gohr 123cf2dcf1bSAndreas Gohr protected function initFromRemoteData($data) 124cf2dcf1bSAndreas Gohr { 125cf2dcf1bSAndreas Gohr if (!isset($data['plugin'])) throw new RuntimeException('Invalid remote data'); 126cf2dcf1bSAndreas Gohr 127a1e045f7SAndreas Gohr [$type, $base] = $this->idToTypeBase($data['plugin']); 128cf2dcf1bSAndreas Gohr $this->remoteInfo = $data; 129cf2dcf1bSAndreas Gohr $this->type = $type; 130cf2dcf1bSAndreas Gohr $this->base = $base; 131cf2dcf1bSAndreas Gohr 132cf2dcf1bSAndreas Gohr if ($this->isInstalled()) { 133cf2dcf1bSAndreas Gohr $this->currentDir = $this->getInstallDir(); 134cf2dcf1bSAndreas Gohr $this->readLocalInfo(); 135cf2dcf1bSAndreas Gohr } 136cf2dcf1bSAndreas Gohr } 137cf2dcf1bSAndreas Gohr 138cf2dcf1bSAndreas Gohr // endregion 139cf2dcf1bSAndreas Gohr 140cf2dcf1bSAndreas Gohr // region Getters 141cf2dcf1bSAndreas Gohr 142cf2dcf1bSAndreas Gohr /** 143cf2dcf1bSAndreas Gohr * @return string The extension id (same as base but prefixed with "template:" for templates) 144cf2dcf1bSAndreas Gohr */ 145cf2dcf1bSAndreas Gohr public function getId() 146cf2dcf1bSAndreas Gohr { 147cf2dcf1bSAndreas Gohr if ($this->type === self::TYPE_TEMPLATE) { 148cf2dcf1bSAndreas Gohr return self::TYPE_TEMPLATE . ':' . $this->base; 149cf2dcf1bSAndreas Gohr } 150cf2dcf1bSAndreas Gohr return $this->base; 151cf2dcf1bSAndreas Gohr } 152cf2dcf1bSAndreas Gohr 153cf2dcf1bSAndreas Gohr /** 154cf2dcf1bSAndreas Gohr * Get the base name of this extension 155cf2dcf1bSAndreas Gohr * 156cf2dcf1bSAndreas Gohr * @return string 157cf2dcf1bSAndreas Gohr */ 158cf2dcf1bSAndreas Gohr public function getBase() 159cf2dcf1bSAndreas Gohr { 160cf2dcf1bSAndreas Gohr return $this->base; 161cf2dcf1bSAndreas Gohr } 162cf2dcf1bSAndreas Gohr 163cf2dcf1bSAndreas Gohr /** 164cf2dcf1bSAndreas Gohr * Get the type of the extension 165cf2dcf1bSAndreas Gohr * 166cf2dcf1bSAndreas Gohr * @return string "plugin"|"template" 167cf2dcf1bSAndreas Gohr */ 168cf2dcf1bSAndreas Gohr public function getType() 169cf2dcf1bSAndreas Gohr { 170cf2dcf1bSAndreas Gohr return $this->type; 171cf2dcf1bSAndreas Gohr } 172cf2dcf1bSAndreas Gohr 173cf2dcf1bSAndreas Gohr /** 174cf2dcf1bSAndreas Gohr * The current directory of the extension 175cf2dcf1bSAndreas Gohr * 176cf2dcf1bSAndreas Gohr * @return string|null 177cf2dcf1bSAndreas Gohr */ 178cf2dcf1bSAndreas Gohr public function getCurrentDir() 179cf2dcf1bSAndreas Gohr { 180cf2dcf1bSAndreas Gohr // recheck that the current currentDir is still valid 181cf2dcf1bSAndreas Gohr if ($this->currentDir && !is_dir($this->currentDir)) { 182cf2dcf1bSAndreas Gohr $this->currentDir = null; 183cf2dcf1bSAndreas Gohr } 184cf2dcf1bSAndreas Gohr 185cf2dcf1bSAndreas Gohr // if the extension is installed, then the currentDir is the install dir! 186cf2dcf1bSAndreas Gohr if (!$this->currentDir && $this->isInstalled()) { 187cf2dcf1bSAndreas Gohr $this->currentDir = $this->getInstallDir(); 188cf2dcf1bSAndreas Gohr } 189cf2dcf1bSAndreas Gohr 190cf2dcf1bSAndreas Gohr return $this->currentDir; 191cf2dcf1bSAndreas Gohr } 192cf2dcf1bSAndreas Gohr 193cf2dcf1bSAndreas Gohr /** 194cf2dcf1bSAndreas Gohr * Get the directory where this extension should be installed in 195cf2dcf1bSAndreas Gohr * 196cf2dcf1bSAndreas Gohr * Note: this does not mean that the extension is actually installed there 197cf2dcf1bSAndreas Gohr * 198cf2dcf1bSAndreas Gohr * @return string 199cf2dcf1bSAndreas Gohr */ 200cf2dcf1bSAndreas Gohr public function getInstallDir() 201cf2dcf1bSAndreas Gohr { 202cf2dcf1bSAndreas Gohr if ($this->isTemplate()) { 203cf2dcf1bSAndreas Gohr $dir = dirname(tpl_incdir()) . $this->base; 204cf2dcf1bSAndreas Gohr } else { 205cf2dcf1bSAndreas Gohr $dir = DOKU_PLUGIN . $this->base; 206cf2dcf1bSAndreas Gohr } 207cf2dcf1bSAndreas Gohr 208cf2dcf1bSAndreas Gohr return realpath($dir); 209cf2dcf1bSAndreas Gohr } 210cf2dcf1bSAndreas Gohr 211cf2dcf1bSAndreas Gohr 212cf2dcf1bSAndreas Gohr /** 213cf2dcf1bSAndreas Gohr * Get the display name of the extension 214cf2dcf1bSAndreas Gohr * 215cf2dcf1bSAndreas Gohr * @return string 216cf2dcf1bSAndreas Gohr */ 217cf2dcf1bSAndreas Gohr public function getDisplayName() 218cf2dcf1bSAndreas Gohr { 219cf2dcf1bSAndreas Gohr return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType())); 220cf2dcf1bSAndreas Gohr } 221cf2dcf1bSAndreas Gohr 222cf2dcf1bSAndreas Gohr /** 223cf2dcf1bSAndreas Gohr * Get the author name of the extension 224cf2dcf1bSAndreas Gohr * 225cf2dcf1bSAndreas Gohr * @return string Returns an empty string if the author info is missing 226cf2dcf1bSAndreas Gohr */ 227cf2dcf1bSAndreas Gohr public function getAuthor() 228cf2dcf1bSAndreas Gohr { 229cf2dcf1bSAndreas Gohr return $this->getTag('author'); 230cf2dcf1bSAndreas Gohr } 231cf2dcf1bSAndreas Gohr 232cf2dcf1bSAndreas Gohr /** 233cf2dcf1bSAndreas Gohr * Get the email of the author of the extension if there is any 234cf2dcf1bSAndreas Gohr * 235cf2dcf1bSAndreas Gohr * @return string Returns an empty string if the email info is missing 236cf2dcf1bSAndreas Gohr */ 237cf2dcf1bSAndreas Gohr public function getEmail() 238cf2dcf1bSAndreas Gohr { 239cf2dcf1bSAndreas Gohr // email is only in the local data 240cf2dcf1bSAndreas Gohr return $this->localInfo['email'] ?? ''; 241cf2dcf1bSAndreas Gohr } 242cf2dcf1bSAndreas Gohr 243cf2dcf1bSAndreas Gohr /** 244cf2dcf1bSAndreas Gohr * Get the email id, i.e. the md5sum of the email 245cf2dcf1bSAndreas Gohr * 246cf2dcf1bSAndreas Gohr * @return string Empty string if no email is available 247cf2dcf1bSAndreas Gohr */ 248cf2dcf1bSAndreas Gohr public function getEmailID() 249cf2dcf1bSAndreas Gohr { 250cf2dcf1bSAndreas Gohr if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid']; 251cf2dcf1bSAndreas Gohr if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']); 252cf2dcf1bSAndreas Gohr return ''; 253cf2dcf1bSAndreas Gohr } 254cf2dcf1bSAndreas Gohr 255cf2dcf1bSAndreas Gohr /** 256cf2dcf1bSAndreas Gohr * Get the description of the extension 257cf2dcf1bSAndreas Gohr * 258cf2dcf1bSAndreas Gohr * @return string Empty string if no description is available 259cf2dcf1bSAndreas Gohr */ 260cf2dcf1bSAndreas Gohr public function getDescription() 261cf2dcf1bSAndreas Gohr { 262cf2dcf1bSAndreas Gohr return $this->getTag(['desc', 'description']); 263cf2dcf1bSAndreas Gohr } 264cf2dcf1bSAndreas Gohr 265cf2dcf1bSAndreas Gohr /** 266cf2dcf1bSAndreas Gohr * Get the URL of the extension, usually a page on dokuwiki.org 267cf2dcf1bSAndreas Gohr * 268cf2dcf1bSAndreas Gohr * @return string 269cf2dcf1bSAndreas Gohr */ 270cf2dcf1bSAndreas Gohr public function getURL() 271cf2dcf1bSAndreas Gohr { 272cf2dcf1bSAndreas Gohr return $this->getTag( 273cf2dcf1bSAndreas Gohr 'url', 274cf2dcf1bSAndreas Gohr 'https://www.dokuwiki.org/' . 275cf2dcf1bSAndreas Gohr ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase() 276cf2dcf1bSAndreas Gohr ); 277cf2dcf1bSAndreas Gohr } 278cf2dcf1bSAndreas Gohr 279cf2dcf1bSAndreas Gohr /** 280*7c9966a5SAndreas Gohr * Get the version of the extension that is actually installed 281*7c9966a5SAndreas Gohr * 282*7c9966a5SAndreas Gohr * Returns an empty string if the version is not available 283*7c9966a5SAndreas Gohr * 284*7c9966a5SAndreas Gohr * @return string 285*7c9966a5SAndreas Gohr */ 286*7c9966a5SAndreas Gohr public function getInstalledVersion() 287*7c9966a5SAndreas Gohr { 288*7c9966a5SAndreas Gohr return $this->localInfo['date'] ?? ''; 289*7c9966a5SAndreas Gohr } 290*7c9966a5SAndreas Gohr 291*7c9966a5SAndreas Gohr /** 292cf2dcf1bSAndreas Gohr * Is this extension a template? 293cf2dcf1bSAndreas Gohr * 294cf2dcf1bSAndreas Gohr * @return bool false if it is a plugin 295cf2dcf1bSAndreas Gohr */ 296cf2dcf1bSAndreas Gohr public function isTemplate() 297cf2dcf1bSAndreas Gohr { 298cf2dcf1bSAndreas Gohr return $this->type === self::TYPE_TEMPLATE; 299cf2dcf1bSAndreas Gohr } 300cf2dcf1bSAndreas Gohr 301cf2dcf1bSAndreas Gohr /** 302cf2dcf1bSAndreas Gohr * Is the extension installed locally? 303cf2dcf1bSAndreas Gohr * 304cf2dcf1bSAndreas Gohr * @return bool 305cf2dcf1bSAndreas Gohr */ 306cf2dcf1bSAndreas Gohr public function isInstalled() 307cf2dcf1bSAndreas Gohr { 308cf2dcf1bSAndreas Gohr return is_dir($this->getInstallDir()); 309cf2dcf1bSAndreas Gohr } 310cf2dcf1bSAndreas Gohr 311cf2dcf1bSAndreas Gohr /** 312cf2dcf1bSAndreas Gohr * Is the extension under git control? 313cf2dcf1bSAndreas Gohr * 314cf2dcf1bSAndreas Gohr * @return bool 315cf2dcf1bSAndreas Gohr */ 316cf2dcf1bSAndreas Gohr public function isGitControlled() 317cf2dcf1bSAndreas Gohr { 318cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) return false; 319cf2dcf1bSAndreas Gohr return file_exists($this->getInstallDir() . '/.git'); 320cf2dcf1bSAndreas Gohr } 321cf2dcf1bSAndreas Gohr 322cf2dcf1bSAndreas Gohr /** 323cf2dcf1bSAndreas Gohr * If the extension is bundled 324cf2dcf1bSAndreas Gohr * 325cf2dcf1bSAndreas Gohr * @return bool If the extension is bundled 326cf2dcf1bSAndreas Gohr */ 327cf2dcf1bSAndreas Gohr public function isBundled() 328cf2dcf1bSAndreas Gohr { 329cf2dcf1bSAndreas Gohr $this->loadRemoteInfo(); 330cf2dcf1bSAndreas Gohr return $this->remoteInfo['bundled'] ?? in_array( 331cf2dcf1bSAndreas Gohr $this->getId(), 332cf2dcf1bSAndreas Gohr [ 333cf2dcf1bSAndreas Gohr 'authad', 334cf2dcf1bSAndreas Gohr 'authldap', 335cf2dcf1bSAndreas Gohr 'authpdo', 336cf2dcf1bSAndreas Gohr 'authplain', 337cf2dcf1bSAndreas Gohr 'acl', 338cf2dcf1bSAndreas Gohr 'config', 339cf2dcf1bSAndreas Gohr 'extension', 340cf2dcf1bSAndreas Gohr 'info', 341cf2dcf1bSAndreas Gohr 'popularity', 342cf2dcf1bSAndreas Gohr 'revert', 343cf2dcf1bSAndreas Gohr 'safefnrecode', 344cf2dcf1bSAndreas Gohr 'styling', 345cf2dcf1bSAndreas Gohr 'testing', 346cf2dcf1bSAndreas Gohr 'usermanager', 347cf2dcf1bSAndreas Gohr 'logviewer', 348cf2dcf1bSAndreas Gohr 'template:dokuwiki' 349cf2dcf1bSAndreas Gohr ] 350cf2dcf1bSAndreas Gohr ); 351cf2dcf1bSAndreas Gohr } 352cf2dcf1bSAndreas Gohr 353cf2dcf1bSAndreas Gohr /** 354cf2dcf1bSAndreas Gohr * Is the extension protected against any modification (disable/uninstall) 355cf2dcf1bSAndreas Gohr * 356cf2dcf1bSAndreas Gohr * @return bool if the extension is protected 357cf2dcf1bSAndreas Gohr */ 358cf2dcf1bSAndreas Gohr public function isProtected() 359cf2dcf1bSAndreas Gohr { 360cf2dcf1bSAndreas Gohr // never allow deinstalling the current auth plugin: 361cf2dcf1bSAndreas Gohr global $conf; 362cf2dcf1bSAndreas Gohr if ($this->getId() == $conf['authtype']) return true; 363cf2dcf1bSAndreas Gohr 364cf2dcf1bSAndreas Gohr // FIXME disallow current template to be uninstalled 365cf2dcf1bSAndreas Gohr 366cf2dcf1bSAndreas Gohr /** @var PluginController $plugin_controller */ 367cf2dcf1bSAndreas Gohr global $plugin_controller; 368cf2dcf1bSAndreas Gohr $cascade = $plugin_controller->getCascade(); 369cf2dcf1bSAndreas Gohr return ($cascade['protected'][$this->getId()] ?? false); 370cf2dcf1bSAndreas Gohr } 371cf2dcf1bSAndreas Gohr 372cf2dcf1bSAndreas Gohr /** 373cf2dcf1bSAndreas Gohr * Is the extension installed in the correct directory? 374cf2dcf1bSAndreas Gohr * 375cf2dcf1bSAndreas Gohr * @return bool 376cf2dcf1bSAndreas Gohr */ 377cf2dcf1bSAndreas Gohr public function isInWrongFolder() 378cf2dcf1bSAndreas Gohr { 379cf2dcf1bSAndreas Gohr return $this->getInstallDir() != $this->currentDir; 380cf2dcf1bSAndreas Gohr } 381cf2dcf1bSAndreas Gohr 382cf2dcf1bSAndreas Gohr /** 383cf2dcf1bSAndreas Gohr * Is the extension enabled? 384cf2dcf1bSAndreas Gohr * 385cf2dcf1bSAndreas Gohr * @return bool 386cf2dcf1bSAndreas Gohr */ 387cf2dcf1bSAndreas Gohr public function isEnabled() 388cf2dcf1bSAndreas Gohr { 389cf2dcf1bSAndreas Gohr global $conf; 390cf2dcf1bSAndreas Gohr if ($this->isTemplate()) { 391cf2dcf1bSAndreas Gohr return ($conf['template'] == $this->getBase()); 392cf2dcf1bSAndreas Gohr } 393cf2dcf1bSAndreas Gohr 394cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 395cf2dcf1bSAndreas Gohr global $plugin_controller; 396cf2dcf1bSAndreas Gohr return $plugin_controller->isEnabled($this->base); 397cf2dcf1bSAndreas Gohr } 398cf2dcf1bSAndreas Gohr 399cf2dcf1bSAndreas Gohr // endregion 400cf2dcf1bSAndreas Gohr 401*7c9966a5SAndreas Gohr // region Remote Info 402*7c9966a5SAndreas Gohr 403*7c9966a5SAndreas Gohr /** 404*7c9966a5SAndreas Gohr * Get the date of the last available update 405*7c9966a5SAndreas Gohr * 406*7c9966a5SAndreas Gohr * @return string yyyy-mm-dd 407*7c9966a5SAndreas Gohr */ 408*7c9966a5SAndreas Gohr public function getLastUpdate() 409*7c9966a5SAndreas Gohr { 410*7c9966a5SAndreas Gohr return $this->getRemoteTag('lastupdate'); 411*7c9966a5SAndreas Gohr } 412*7c9966a5SAndreas Gohr 413*7c9966a5SAndreas Gohr /** 414*7c9966a5SAndreas Gohr * Get a list of tags this extension is tagged with at dokuwiki.org 415*7c9966a5SAndreas Gohr * 416*7c9966a5SAndreas Gohr * @return string[] 417*7c9966a5SAndreas Gohr */ 418*7c9966a5SAndreas Gohr public function getTags() 419*7c9966a5SAndreas Gohr { 420*7c9966a5SAndreas Gohr return $this->getRemoteTag('tags', []); 421*7c9966a5SAndreas Gohr } 422*7c9966a5SAndreas Gohr 423*7c9966a5SAndreas Gohr /** 424*7c9966a5SAndreas Gohr * Get the popularity of the extension 425*7c9966a5SAndreas Gohr * 426*7c9966a5SAndreas Gohr * This is a float between 0 and 1 427*7c9966a5SAndreas Gohr * 428*7c9966a5SAndreas Gohr * @return float 429*7c9966a5SAndreas Gohr */ 430*7c9966a5SAndreas Gohr public function getPopularity() 431*7c9966a5SAndreas Gohr { 432*7c9966a5SAndreas Gohr return (float)$this->getRemoteTag('popularity', 0); 433*7c9966a5SAndreas Gohr } 434*7c9966a5SAndreas Gohr 435*7c9966a5SAndreas Gohr /** 436*7c9966a5SAndreas Gohr * Get the text of the update message if there is any 437*7c9966a5SAndreas Gohr * 438*7c9966a5SAndreas Gohr * @return string 439*7c9966a5SAndreas Gohr */ 440*7c9966a5SAndreas Gohr public function getUpdateMessage() 441*7c9966a5SAndreas Gohr { 442*7c9966a5SAndreas Gohr return $this->getRemoteTag('updatemessage'); 443*7c9966a5SAndreas Gohr } 444*7c9966a5SAndreas Gohr 445*7c9966a5SAndreas Gohr /** 446*7c9966a5SAndreas Gohr * Get the text of the security warning if there is any 447*7c9966a5SAndreas Gohr * 448*7c9966a5SAndreas Gohr * @return string 449*7c9966a5SAndreas Gohr */ 450*7c9966a5SAndreas Gohr public function getSecurityWarning() 451*7c9966a5SAndreas Gohr { 452*7c9966a5SAndreas Gohr return $this->getRemoteTag('securitywarning'); 453*7c9966a5SAndreas Gohr } 454*7c9966a5SAndreas Gohr 455*7c9966a5SAndreas Gohr /** 456*7c9966a5SAndreas Gohr * Get the text of the security issue if there is any 457*7c9966a5SAndreas Gohr * 458*7c9966a5SAndreas Gohr * @return string 459*7c9966a5SAndreas Gohr */ 460*7c9966a5SAndreas Gohr public function getSecurityIssue() 461*7c9966a5SAndreas Gohr { 462*7c9966a5SAndreas Gohr return $this->getRemoteTag('securityissue'); 463*7c9966a5SAndreas Gohr } 464*7c9966a5SAndreas Gohr 465*7c9966a5SAndreas Gohr /** 466*7c9966a5SAndreas Gohr * Get the URL of the screenshot of the extension if there is any 467*7c9966a5SAndreas Gohr * 468*7c9966a5SAndreas Gohr * @return string 469*7c9966a5SAndreas Gohr */ 470*7c9966a5SAndreas Gohr public function getScreenshotURL() 471*7c9966a5SAndreas Gohr { 472*7c9966a5SAndreas Gohr return $this->getRemoteTag('screenshoturl'); 473*7c9966a5SAndreas Gohr } 474*7c9966a5SAndreas Gohr 475*7c9966a5SAndreas Gohr /** 476*7c9966a5SAndreas Gohr * Get the URL of the thumbnail of the extension if there is any 477*7c9966a5SAndreas Gohr * 478*7c9966a5SAndreas Gohr * @return string 479*7c9966a5SAndreas Gohr */ 480*7c9966a5SAndreas Gohr public function getThumbnailURL() 481*7c9966a5SAndreas Gohr { 482*7c9966a5SAndreas Gohr return $this->getRemoteTag('thumbnailurl'); 483*7c9966a5SAndreas Gohr } 484*7c9966a5SAndreas Gohr 485*7c9966a5SAndreas Gohr /** 486*7c9966a5SAndreas Gohr * Get the download URL of the extension if there is any 487*7c9966a5SAndreas Gohr * 488*7c9966a5SAndreas Gohr * @return string 489*7c9966a5SAndreas Gohr */ 490*7c9966a5SAndreas Gohr public function getDownloadURL() 491*7c9966a5SAndreas Gohr { 492*7c9966a5SAndreas Gohr return $this->getRemoteTag('downloadurl'); 493*7c9966a5SAndreas Gohr } 494*7c9966a5SAndreas Gohr 495*7c9966a5SAndreas Gohr /** 496*7c9966a5SAndreas Gohr * Get the bug tracker URL of the extension if there is any 497*7c9966a5SAndreas Gohr * 498*7c9966a5SAndreas Gohr * @return string 499*7c9966a5SAndreas Gohr */ 500*7c9966a5SAndreas Gohr public function getBugtrackerURL() 501*7c9966a5SAndreas Gohr { 502*7c9966a5SAndreas Gohr return $this->getRemoteTag('bugtracker'); 503*7c9966a5SAndreas Gohr } 504*7c9966a5SAndreas Gohr 505*7c9966a5SAndreas Gohr /** 506*7c9966a5SAndreas Gohr * Get the URL of the source repository if there is any 507*7c9966a5SAndreas Gohr * 508*7c9966a5SAndreas Gohr * @return string 509*7c9966a5SAndreas Gohr */ 510*7c9966a5SAndreas Gohr public function getSourcerepoURL() 511*7c9966a5SAndreas Gohr { 512*7c9966a5SAndreas Gohr return $this->getRemoteTag('sourcerepo'); 513*7c9966a5SAndreas Gohr } 514*7c9966a5SAndreas Gohr 515*7c9966a5SAndreas Gohr /** 516*7c9966a5SAndreas Gohr * Get the donation URL of the extension if there is any 517*7c9966a5SAndreas Gohr * 518*7c9966a5SAndreas Gohr * @return string 519*7c9966a5SAndreas Gohr */ 520*7c9966a5SAndreas Gohr public function getDonationURL() 521*7c9966a5SAndreas Gohr { 522*7c9966a5SAndreas Gohr return $this->getRemoteTag('donationurl'); 523*7c9966a5SAndreas Gohr } 524*7c9966a5SAndreas Gohr 525*7c9966a5SAndreas Gohr // endregion 526*7c9966a5SAndreas Gohr 527cf2dcf1bSAndreas Gohr // region Actions 528cf2dcf1bSAndreas Gohr 529cf2dcf1bSAndreas Gohr /** 530cf2dcf1bSAndreas Gohr * Install or update the extension 531cf2dcf1bSAndreas Gohr * 532cf2dcf1bSAndreas Gohr * @throws Exception 533cf2dcf1bSAndreas Gohr */ 534cf2dcf1bSAndreas Gohr public function installOrUpdate() 535cf2dcf1bSAndreas Gohr { 536cf2dcf1bSAndreas Gohr $installer = new Installer(true); 537cf2dcf1bSAndreas Gohr $installer->installFromUrl( 538cf2dcf1bSAndreas Gohr $this->getURL(), 539cf2dcf1bSAndreas Gohr $this->getBase(), 540cf2dcf1bSAndreas Gohr ); 541cf2dcf1bSAndreas Gohr } 542cf2dcf1bSAndreas Gohr 543cf2dcf1bSAndreas Gohr /** 544cf2dcf1bSAndreas Gohr * Uninstall the extension 545cf2dcf1bSAndreas Gohr * @throws Exception 546cf2dcf1bSAndreas Gohr */ 547cf2dcf1bSAndreas Gohr public function uninstall() 548cf2dcf1bSAndreas Gohr { 549cf2dcf1bSAndreas Gohr $installer = new Installer(true); 550cf2dcf1bSAndreas Gohr $installer->uninstall($this); 551cf2dcf1bSAndreas Gohr } 552cf2dcf1bSAndreas Gohr 553cf2dcf1bSAndreas Gohr /** 554cf2dcf1bSAndreas Gohr * Enable the extension 555cf2dcf1bSAndreas Gohr * @todo I'm unsure if this code should be here or part of Installer 556cf2dcf1bSAndreas Gohr * @throws Exception 557cf2dcf1bSAndreas Gohr */ 558cf2dcf1bSAndreas Gohr public function enable() 559cf2dcf1bSAndreas Gohr { 560cf2dcf1bSAndreas Gohr if ($this->isTemplate()) throw new Exception('notimplemented'); 561cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) throw new Exception('notinstalled'); 562cf2dcf1bSAndreas Gohr if ($this->isEnabled()) throw new Exception('alreadyenabled'); 563cf2dcf1bSAndreas Gohr 564cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 565cf2dcf1bSAndreas Gohr global $plugin_controller; 566cf2dcf1bSAndreas Gohr if (!$plugin_controller->enable($this->base)) { 567cf2dcf1bSAndreas Gohr throw new Exception('pluginlistsaveerror'); 568cf2dcf1bSAndreas Gohr } 569cf2dcf1bSAndreas Gohr Installer::purgeCache(); 570cf2dcf1bSAndreas Gohr } 571cf2dcf1bSAndreas Gohr 572cf2dcf1bSAndreas Gohr /** 573cf2dcf1bSAndreas Gohr * Disable the extension 574cf2dcf1bSAndreas Gohr * @todo I'm unsure if this code should be here or part of Installer 575cf2dcf1bSAndreas Gohr * @throws Exception 576cf2dcf1bSAndreas Gohr */ 577cf2dcf1bSAndreas Gohr public function disable() 578cf2dcf1bSAndreas Gohr { 579cf2dcf1bSAndreas Gohr if ($this->isTemplate()) throw new Exception('notimplemented'); 580cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) throw new Exception('notinstalled'); 581cf2dcf1bSAndreas Gohr if (!$this->isEnabled()) throw new Exception('alreadydisabled'); 582cf2dcf1bSAndreas Gohr if ($this->isProtected()) throw new Exception('error_disable_protected'); 583cf2dcf1bSAndreas Gohr 584cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 585cf2dcf1bSAndreas Gohr global $plugin_controller; 586cf2dcf1bSAndreas Gohr if (!$plugin_controller->disable($this->base)) { 587cf2dcf1bSAndreas Gohr throw new Exception('pluginlistsaveerror'); 588cf2dcf1bSAndreas Gohr } 589cf2dcf1bSAndreas Gohr Installer::purgeCache(); 590cf2dcf1bSAndreas Gohr } 591cf2dcf1bSAndreas Gohr 592cf2dcf1bSAndreas Gohr // endregion 593cf2dcf1bSAndreas Gohr 594cf2dcf1bSAndreas Gohr // region Meta Data Management 595cf2dcf1bSAndreas Gohr 596cf2dcf1bSAndreas Gohr /** 597*7c9966a5SAndreas Gohr * Access the Manager for this extension 598cf2dcf1bSAndreas Gohr * 599*7c9966a5SAndreas Gohr * @return Manager 600cf2dcf1bSAndreas Gohr */ 601*7c9966a5SAndreas Gohr public function getManager() 602cf2dcf1bSAndreas Gohr { 603*7c9966a5SAndreas Gohr if ($this->manager === null) { 604*7c9966a5SAndreas Gohr $this->manager = new Manager($this); 605cf2dcf1bSAndreas Gohr } 606*7c9966a5SAndreas Gohr return $this->manager; 607cf2dcf1bSAndreas Gohr } 608cf2dcf1bSAndreas Gohr 609cf2dcf1bSAndreas Gohr /** 610cf2dcf1bSAndreas Gohr * Reads the info file of the extension if available and fills the localInfo array 611cf2dcf1bSAndreas Gohr */ 612cf2dcf1bSAndreas Gohr protected function readLocalInfo() 613cf2dcf1bSAndreas Gohr { 614a1e045f7SAndreas Gohr if (!$this->getCurrentDir()) return; 615cf2dcf1bSAndreas Gohr $file = $this->currentDir . '/' . $this->type . '.info.txt'; 616cf2dcf1bSAndreas Gohr if (!is_readable($file)) return; 617cf2dcf1bSAndreas Gohr $this->localInfo = confToHash($file, true); 618cf2dcf1bSAndreas Gohr $this->localInfo = array_filter($this->localInfo); // remove all falsy keys 619cf2dcf1bSAndreas Gohr } 620cf2dcf1bSAndreas Gohr 621cf2dcf1bSAndreas Gohr /** 622cf2dcf1bSAndreas Gohr * Fetches the remote info from the repository 623cf2dcf1bSAndreas Gohr * 624cf2dcf1bSAndreas Gohr * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case 625cf2dcf1bSAndreas Gohr */ 626cf2dcf1bSAndreas Gohr protected function loadRemoteInfo() 627cf2dcf1bSAndreas Gohr { 628cf2dcf1bSAndreas Gohr if ($this->remoteInfo) return; 629cf2dcf1bSAndreas Gohr $remote = Repository::getInstance(); 630cf2dcf1bSAndreas Gohr try { 631cf2dcf1bSAndreas Gohr $this->remoteInfo = (array)$remote->getExtensionData($this->getId()); 632cf2dcf1bSAndreas Gohr } catch (Exception $e) { 633cf2dcf1bSAndreas Gohr $this->remoteInfo = []; 634cf2dcf1bSAndreas Gohr } 635cf2dcf1bSAndreas Gohr } 636cf2dcf1bSAndreas Gohr 637cf2dcf1bSAndreas Gohr /** 638cf2dcf1bSAndreas Gohr * Read information from either local or remote info 639cf2dcf1bSAndreas Gohr * 640*7c9966a5SAndreas Gohr * Always prefers local info over remote info. Giving multiple keys is useful when the 641*7c9966a5SAndreas Gohr * key has been renamed in the past or if local and remote keys might differ. 642cf2dcf1bSAndreas Gohr * 643cf2dcf1bSAndreas Gohr * @param string|string[] $tag one or multiple keys to check 644cf2dcf1bSAndreas Gohr * @param mixed $default 645cf2dcf1bSAndreas Gohr * @return mixed 646cf2dcf1bSAndreas Gohr */ 647cf2dcf1bSAndreas Gohr protected function getTag($tag, $default = '') 648cf2dcf1bSAndreas Gohr { 649cf2dcf1bSAndreas Gohr foreach ((array)$tag as $t) { 650cf2dcf1bSAndreas Gohr if (isset($this->localInfo[$t])) return $this->localInfo[$t]; 651cf2dcf1bSAndreas Gohr } 652*7c9966a5SAndreas Gohr 653*7c9966a5SAndreas Gohr return $this->getRemoteTag($tag, $default); 654*7c9966a5SAndreas Gohr } 655*7c9966a5SAndreas Gohr 656*7c9966a5SAndreas Gohr /** 657*7c9966a5SAndreas Gohr * Read information from remote info 658*7c9966a5SAndreas Gohr * 659*7c9966a5SAndreas Gohr * @param string|string[] $tag one or mutiple keys to check 660*7c9966a5SAndreas Gohr * @param mixed $default 661*7c9966a5SAndreas Gohr * @return mixed 662*7c9966a5SAndreas Gohr */ 663*7c9966a5SAndreas Gohr protected function getRemoteTag($tag, $default = '') 664*7c9966a5SAndreas Gohr { 665cf2dcf1bSAndreas Gohr $this->loadRemoteInfo(); 666cf2dcf1bSAndreas Gohr foreach ((array)$tag as $t) { 667cf2dcf1bSAndreas Gohr if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t]; 668cf2dcf1bSAndreas Gohr } 669cf2dcf1bSAndreas Gohr return $default; 670cf2dcf1bSAndreas Gohr } 671cf2dcf1bSAndreas Gohr 672cf2dcf1bSAndreas Gohr // endregion 673a1e045f7SAndreas Gohr 674a1e045f7SAndreas Gohr // region utilities 675a1e045f7SAndreas Gohr 676a1e045f7SAndreas Gohr /** 677a1e045f7SAndreas Gohr * Convert an extension id to a type and base 678a1e045f7SAndreas Gohr * 679a1e045f7SAndreas Gohr * @param string $id 680a1e045f7SAndreas Gohr * @return array [type, base] 681a1e045f7SAndreas Gohr */ 682a1e045f7SAndreas Gohr protected function idToTypeBase($id) 683a1e045f7SAndreas Gohr { 684a1e045f7SAndreas Gohr [$type, $base] = sexplode(':', $id, 2); 685a1e045f7SAndreas Gohr if ($base === null) { 686a1e045f7SAndreas Gohr $base = $type; 687a1e045f7SAndreas Gohr $type = self::TYPE_PLUGIN; 688a1e045f7SAndreas Gohr } elseif ($type === self::TYPE_TEMPLATE) { 689a1e045f7SAndreas Gohr $type = self::TYPE_TEMPLATE; 690a1e045f7SAndreas Gohr } else { 691a1e045f7SAndreas Gohr throw new RuntimeException('Invalid extension id: ' . $id); 692a1e045f7SAndreas Gohr } 693a1e045f7SAndreas Gohr 694a1e045f7SAndreas Gohr return [$type, $base]; 695a1e045f7SAndreas Gohr } 696*7c9966a5SAndreas Gohr /** 697*7c9966a5SAndreas Gohr * @return string 698*7c9966a5SAndreas Gohr */ 699*7c9966a5SAndreas Gohr public function __toString() 700*7c9966a5SAndreas Gohr { 701*7c9966a5SAndreas Gohr return $this->getId(); 702*7c9966a5SAndreas Gohr } 703*7c9966a5SAndreas Gohr 704a1e045f7SAndreas Gohr // endregion 705cf2dcf1bSAndreas Gohr} 706