1*cf2dcf1bSAndreas Gohr<?php 2*cf2dcf1bSAndreas Gohr 3*cf2dcf1bSAndreas Gohrnamespace dokuwiki\plugin\extension; 4*cf2dcf1bSAndreas Gohr 5*cf2dcf1bSAndreas Gohruse dokuwiki\Extension\PluginController; 6*cf2dcf1bSAndreas Gohruse dokuwiki\Utf8\PhpString; 7*cf2dcf1bSAndreas Gohruse RuntimeException; 8*cf2dcf1bSAndreas Gohr 9*cf2dcf1bSAndreas Gohrclass Extension 10*cf2dcf1bSAndreas Gohr{ 11*cf2dcf1bSAndreas Gohr const TYPE_PLUGIN = 'plugin'; 12*cf2dcf1bSAndreas Gohr const TYPE_TEMPLATE = 'template'; 13*cf2dcf1bSAndreas Gohr 14*cf2dcf1bSAndreas Gohr /** @var string "plugin"|"template" */ 15*cf2dcf1bSAndreas Gohr protected string $type = self::TYPE_PLUGIN; 16*cf2dcf1bSAndreas Gohr 17*cf2dcf1bSAndreas Gohr /** @var string The base name of this extension */ 18*cf2dcf1bSAndreas Gohr protected string $base; 19*cf2dcf1bSAndreas Gohr 20*cf2dcf1bSAndreas Gohr /** @var string|null The current location of this extension */ 21*cf2dcf1bSAndreas Gohr protected ?string $currentDir; 22*cf2dcf1bSAndreas Gohr 23*cf2dcf1bSAndreas Gohr /** @var array The local info array of the extension */ 24*cf2dcf1bSAndreas Gohr protected array $localInfo = []; 25*cf2dcf1bSAndreas Gohr 26*cf2dcf1bSAndreas Gohr /** @var array The remote info array of the extension */ 27*cf2dcf1bSAndreas Gohr protected array $remoteInfo = []; 28*cf2dcf1bSAndreas Gohr 29*cf2dcf1bSAndreas Gohr /** @var array The manager info array of the extension */ 30*cf2dcf1bSAndreas Gohr protected array $managerInfo = []; 31*cf2dcf1bSAndreas Gohr 32*cf2dcf1bSAndreas Gohr // region Constructors 33*cf2dcf1bSAndreas Gohr 34*cf2dcf1bSAndreas Gohr /** 35*cf2dcf1bSAndreas Gohr * The main constructor is private to force the use of the factory methods 36*cf2dcf1bSAndreas Gohr */ 37*cf2dcf1bSAndreas Gohr protected function __construct() 38*cf2dcf1bSAndreas Gohr { 39*cf2dcf1bSAndreas Gohr } 40*cf2dcf1bSAndreas Gohr 41*cf2dcf1bSAndreas Gohr /** 42*cf2dcf1bSAndreas Gohr * Initializes an extension from a directory 43*cf2dcf1bSAndreas Gohr * 44*cf2dcf1bSAndreas Gohr * The given directory might be the one where the extension has already been installed to 45*cf2dcf1bSAndreas Gohr * or it might be the extracted source in some temporary directory. 46*cf2dcf1bSAndreas Gohr * 47*cf2dcf1bSAndreas Gohr * @param string $dir Where the extension code is currently located 48*cf2dcf1bSAndreas Gohr * @param string|null $type TYPE_PLUGIN|TYPE_TEMPLATE, null for auto-detection 49*cf2dcf1bSAndreas Gohr * @param string $base The base name of the extension, null for auto-detection 50*cf2dcf1bSAndreas Gohr * @return Extension 51*cf2dcf1bSAndreas Gohr */ 52*cf2dcf1bSAndreas Gohr public static function createFromDirectory($dir, $type = null, $base = null) 53*cf2dcf1bSAndreas Gohr { 54*cf2dcf1bSAndreas Gohr $extension = new self(); 55*cf2dcf1bSAndreas Gohr $extension->initFromDirectory($dir, $type, $base); 56*cf2dcf1bSAndreas Gohr return $extension; 57*cf2dcf1bSAndreas Gohr } 58*cf2dcf1bSAndreas Gohr 59*cf2dcf1bSAndreas Gohr protected function initFromDirectory($dir, $type = null, $base = null) 60*cf2dcf1bSAndreas Gohr { 61*cf2dcf1bSAndreas Gohr if (!is_dir($dir)) throw new RuntimeException('Directory not found: ' . $dir); 62*cf2dcf1bSAndreas Gohr $this->currentDir = realpath($dir); 63*cf2dcf1bSAndreas Gohr 64*cf2dcf1bSAndreas Gohr if ($type === null || $type === self::TYPE_TEMPLATE) { 65*cf2dcf1bSAndreas Gohr if ( 66*cf2dcf1bSAndreas Gohr file_exists($dir . '/template.info.php') || 67*cf2dcf1bSAndreas Gohr file_exists($dir . '/style.ini') || 68*cf2dcf1bSAndreas Gohr file_exists($dir . '/main.php') || 69*cf2dcf1bSAndreas Gohr file_exists($dir . '/detail.php') || 70*cf2dcf1bSAndreas Gohr file_exists($dir . '/mediamanager.php') 71*cf2dcf1bSAndreas Gohr ) { 72*cf2dcf1bSAndreas Gohr $this->type = self::TYPE_TEMPLATE; 73*cf2dcf1bSAndreas Gohr } 74*cf2dcf1bSAndreas Gohr } else { 75*cf2dcf1bSAndreas Gohr $this->type = self::TYPE_PLUGIN; 76*cf2dcf1bSAndreas Gohr } 77*cf2dcf1bSAndreas Gohr 78*cf2dcf1bSAndreas Gohr $this->readLocalInfo(); 79*cf2dcf1bSAndreas Gohr 80*cf2dcf1bSAndreas Gohr if ($base !== null) { 81*cf2dcf1bSAndreas Gohr $this->base = $base; 82*cf2dcf1bSAndreas Gohr } elseif (isset($this->localInfo['base'])) { 83*cf2dcf1bSAndreas Gohr $this->base = $this->localInfo['base']; 84*cf2dcf1bSAndreas Gohr } else { 85*cf2dcf1bSAndreas Gohr $this->base = basename($dir); 86*cf2dcf1bSAndreas Gohr } 87*cf2dcf1bSAndreas Gohr } 88*cf2dcf1bSAndreas Gohr 89*cf2dcf1bSAndreas Gohr /** 90*cf2dcf1bSAndreas Gohr * Initializes an extension from remote data 91*cf2dcf1bSAndreas Gohr * 92*cf2dcf1bSAndreas Gohr * @param array $data The data as returned by the repository api 93*cf2dcf1bSAndreas Gohr * @return Extension 94*cf2dcf1bSAndreas Gohr */ 95*cf2dcf1bSAndreas Gohr public static function createFromRemoteData($data) 96*cf2dcf1bSAndreas Gohr { 97*cf2dcf1bSAndreas Gohr $extension = new self(); 98*cf2dcf1bSAndreas Gohr $extension->initFromRemoteData($data); 99*cf2dcf1bSAndreas Gohr return $extension; 100*cf2dcf1bSAndreas Gohr } 101*cf2dcf1bSAndreas Gohr 102*cf2dcf1bSAndreas Gohr protected function initFromRemoteData($data) 103*cf2dcf1bSAndreas Gohr { 104*cf2dcf1bSAndreas Gohr if (!isset($data['plugin'])) throw new RuntimeException('Invalid remote data'); 105*cf2dcf1bSAndreas Gohr 106*cf2dcf1bSAndreas Gohr [$type, $base] = sexplode(':', $data['plugin'], 2); 107*cf2dcf1bSAndreas Gohr if ($base === null) { 108*cf2dcf1bSAndreas Gohr $base = $type; 109*cf2dcf1bSAndreas Gohr $type = self::TYPE_PLUGIN; 110*cf2dcf1bSAndreas Gohr } else { 111*cf2dcf1bSAndreas Gohr $type = self::TYPE_TEMPLATE; 112*cf2dcf1bSAndreas Gohr } 113*cf2dcf1bSAndreas Gohr 114*cf2dcf1bSAndreas Gohr $this->remoteInfo = $data; 115*cf2dcf1bSAndreas Gohr $this->type = $type; 116*cf2dcf1bSAndreas Gohr $this->base = $base; 117*cf2dcf1bSAndreas Gohr 118*cf2dcf1bSAndreas Gohr if ($this->isInstalled()) { 119*cf2dcf1bSAndreas Gohr $this->currentDir = $this->getInstallDir(); 120*cf2dcf1bSAndreas Gohr $this->readLocalInfo(); 121*cf2dcf1bSAndreas Gohr } 122*cf2dcf1bSAndreas Gohr } 123*cf2dcf1bSAndreas Gohr 124*cf2dcf1bSAndreas Gohr // endregion 125*cf2dcf1bSAndreas Gohr 126*cf2dcf1bSAndreas Gohr // region Getters 127*cf2dcf1bSAndreas Gohr 128*cf2dcf1bSAndreas Gohr /** 129*cf2dcf1bSAndreas Gohr * @return string The extension id (same as base but prefixed with "template:" for templates) 130*cf2dcf1bSAndreas Gohr */ 131*cf2dcf1bSAndreas Gohr public function getId() 132*cf2dcf1bSAndreas Gohr { 133*cf2dcf1bSAndreas Gohr if ($this->type === self::TYPE_TEMPLATE) { 134*cf2dcf1bSAndreas Gohr return self::TYPE_TEMPLATE . ':' . $this->base; 135*cf2dcf1bSAndreas Gohr } 136*cf2dcf1bSAndreas Gohr return $this->base; 137*cf2dcf1bSAndreas Gohr } 138*cf2dcf1bSAndreas Gohr 139*cf2dcf1bSAndreas Gohr /** 140*cf2dcf1bSAndreas Gohr * Get the base name of this extension 141*cf2dcf1bSAndreas Gohr * 142*cf2dcf1bSAndreas Gohr * @return string 143*cf2dcf1bSAndreas Gohr */ 144*cf2dcf1bSAndreas Gohr public function getBase() 145*cf2dcf1bSAndreas Gohr { 146*cf2dcf1bSAndreas Gohr return $this->base; 147*cf2dcf1bSAndreas Gohr } 148*cf2dcf1bSAndreas Gohr 149*cf2dcf1bSAndreas Gohr /** 150*cf2dcf1bSAndreas Gohr * Get the type of the extension 151*cf2dcf1bSAndreas Gohr * 152*cf2dcf1bSAndreas Gohr * @return string "plugin"|"template" 153*cf2dcf1bSAndreas Gohr */ 154*cf2dcf1bSAndreas Gohr public function getType() 155*cf2dcf1bSAndreas Gohr { 156*cf2dcf1bSAndreas Gohr return $this->type; 157*cf2dcf1bSAndreas Gohr } 158*cf2dcf1bSAndreas Gohr 159*cf2dcf1bSAndreas Gohr /** 160*cf2dcf1bSAndreas Gohr * The current directory of the extension 161*cf2dcf1bSAndreas Gohr * 162*cf2dcf1bSAndreas Gohr * @return string|null 163*cf2dcf1bSAndreas Gohr */ 164*cf2dcf1bSAndreas Gohr public function getCurrentDir() 165*cf2dcf1bSAndreas Gohr { 166*cf2dcf1bSAndreas Gohr // recheck that the current currentDir is still valid 167*cf2dcf1bSAndreas Gohr if ($this->currentDir && !is_dir($this->currentDir)) { 168*cf2dcf1bSAndreas Gohr $this->currentDir = null; 169*cf2dcf1bSAndreas Gohr } 170*cf2dcf1bSAndreas Gohr 171*cf2dcf1bSAndreas Gohr // if the extension is installed, then the currentDir is the install dir! 172*cf2dcf1bSAndreas Gohr if (!$this->currentDir && $this->isInstalled()) { 173*cf2dcf1bSAndreas Gohr $this->currentDir = $this->getInstallDir(); 174*cf2dcf1bSAndreas Gohr } 175*cf2dcf1bSAndreas Gohr 176*cf2dcf1bSAndreas Gohr return $this->currentDir; 177*cf2dcf1bSAndreas Gohr } 178*cf2dcf1bSAndreas Gohr 179*cf2dcf1bSAndreas Gohr /** 180*cf2dcf1bSAndreas Gohr * Get the directory where this extension should be installed in 181*cf2dcf1bSAndreas Gohr * 182*cf2dcf1bSAndreas Gohr * Note: this does not mean that the extension is actually installed there 183*cf2dcf1bSAndreas Gohr * 184*cf2dcf1bSAndreas Gohr * @return string 185*cf2dcf1bSAndreas Gohr */ 186*cf2dcf1bSAndreas Gohr public function getInstallDir() 187*cf2dcf1bSAndreas Gohr { 188*cf2dcf1bSAndreas Gohr if ($this->isTemplate()) { 189*cf2dcf1bSAndreas Gohr $dir = dirname(tpl_incdir()) . $this->base; 190*cf2dcf1bSAndreas Gohr } else { 191*cf2dcf1bSAndreas Gohr $dir = DOKU_PLUGIN . $this->base; 192*cf2dcf1bSAndreas Gohr } 193*cf2dcf1bSAndreas Gohr 194*cf2dcf1bSAndreas Gohr return realpath($dir); 195*cf2dcf1bSAndreas Gohr } 196*cf2dcf1bSAndreas Gohr 197*cf2dcf1bSAndreas Gohr 198*cf2dcf1bSAndreas Gohr /** 199*cf2dcf1bSAndreas Gohr * Get the display name of the extension 200*cf2dcf1bSAndreas Gohr * 201*cf2dcf1bSAndreas Gohr * @return string 202*cf2dcf1bSAndreas Gohr */ 203*cf2dcf1bSAndreas Gohr public function getDisplayName() 204*cf2dcf1bSAndreas Gohr { 205*cf2dcf1bSAndreas Gohr return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType())); 206*cf2dcf1bSAndreas Gohr } 207*cf2dcf1bSAndreas Gohr 208*cf2dcf1bSAndreas Gohr /** 209*cf2dcf1bSAndreas Gohr * Get the author name of the extension 210*cf2dcf1bSAndreas Gohr * 211*cf2dcf1bSAndreas Gohr * @return string Returns an empty string if the author info is missing 212*cf2dcf1bSAndreas Gohr */ 213*cf2dcf1bSAndreas Gohr public function getAuthor() 214*cf2dcf1bSAndreas Gohr { 215*cf2dcf1bSAndreas Gohr return $this->getTag('author'); 216*cf2dcf1bSAndreas Gohr } 217*cf2dcf1bSAndreas Gohr 218*cf2dcf1bSAndreas Gohr /** 219*cf2dcf1bSAndreas Gohr * Get the email of the author of the extension if there is any 220*cf2dcf1bSAndreas Gohr * 221*cf2dcf1bSAndreas Gohr * @return string Returns an empty string if the email info is missing 222*cf2dcf1bSAndreas Gohr */ 223*cf2dcf1bSAndreas Gohr public function getEmail() 224*cf2dcf1bSAndreas Gohr { 225*cf2dcf1bSAndreas Gohr // email is only in the local data 226*cf2dcf1bSAndreas Gohr return $this->localInfo['email'] ?? ''; 227*cf2dcf1bSAndreas Gohr } 228*cf2dcf1bSAndreas Gohr 229*cf2dcf1bSAndreas Gohr /** 230*cf2dcf1bSAndreas Gohr * Get the email id, i.e. the md5sum of the email 231*cf2dcf1bSAndreas Gohr * 232*cf2dcf1bSAndreas Gohr * @return string Empty string if no email is available 233*cf2dcf1bSAndreas Gohr */ 234*cf2dcf1bSAndreas Gohr public function getEmailID() 235*cf2dcf1bSAndreas Gohr { 236*cf2dcf1bSAndreas Gohr if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid']; 237*cf2dcf1bSAndreas Gohr if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']); 238*cf2dcf1bSAndreas Gohr return ''; 239*cf2dcf1bSAndreas Gohr } 240*cf2dcf1bSAndreas Gohr 241*cf2dcf1bSAndreas Gohr /** 242*cf2dcf1bSAndreas Gohr * Get the description of the extension 243*cf2dcf1bSAndreas Gohr * 244*cf2dcf1bSAndreas Gohr * @return string Empty string if no description is available 245*cf2dcf1bSAndreas Gohr */ 246*cf2dcf1bSAndreas Gohr public function getDescription() 247*cf2dcf1bSAndreas Gohr { 248*cf2dcf1bSAndreas Gohr return $this->getTag(['desc', 'description']); 249*cf2dcf1bSAndreas Gohr } 250*cf2dcf1bSAndreas Gohr 251*cf2dcf1bSAndreas Gohr /** 252*cf2dcf1bSAndreas Gohr * Get the URL of the extension, usually a page on dokuwiki.org 253*cf2dcf1bSAndreas Gohr * 254*cf2dcf1bSAndreas Gohr * @return string 255*cf2dcf1bSAndreas Gohr */ 256*cf2dcf1bSAndreas Gohr public function getURL() 257*cf2dcf1bSAndreas Gohr { 258*cf2dcf1bSAndreas Gohr return $this->getTag( 259*cf2dcf1bSAndreas Gohr 'url', 260*cf2dcf1bSAndreas Gohr 'https://www.dokuwiki.org/' . 261*cf2dcf1bSAndreas Gohr ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase() 262*cf2dcf1bSAndreas Gohr ); 263*cf2dcf1bSAndreas Gohr } 264*cf2dcf1bSAndreas Gohr 265*cf2dcf1bSAndreas Gohr /** 266*cf2dcf1bSAndreas Gohr * Is this extension a template? 267*cf2dcf1bSAndreas Gohr * 268*cf2dcf1bSAndreas Gohr * @return bool false if it is a plugin 269*cf2dcf1bSAndreas Gohr */ 270*cf2dcf1bSAndreas Gohr public function isTemplate() 271*cf2dcf1bSAndreas Gohr { 272*cf2dcf1bSAndreas Gohr return $this->type === self::TYPE_TEMPLATE; 273*cf2dcf1bSAndreas Gohr } 274*cf2dcf1bSAndreas Gohr 275*cf2dcf1bSAndreas Gohr /** 276*cf2dcf1bSAndreas Gohr * Is the extension installed locally? 277*cf2dcf1bSAndreas Gohr * 278*cf2dcf1bSAndreas Gohr * @return bool 279*cf2dcf1bSAndreas Gohr */ 280*cf2dcf1bSAndreas Gohr public function isInstalled() 281*cf2dcf1bSAndreas Gohr { 282*cf2dcf1bSAndreas Gohr return is_dir($this->getInstallDir()); 283*cf2dcf1bSAndreas Gohr } 284*cf2dcf1bSAndreas Gohr 285*cf2dcf1bSAndreas Gohr /** 286*cf2dcf1bSAndreas Gohr * Is the extension under git control? 287*cf2dcf1bSAndreas Gohr * 288*cf2dcf1bSAndreas Gohr * @return bool 289*cf2dcf1bSAndreas Gohr */ 290*cf2dcf1bSAndreas Gohr public function isGitControlled() 291*cf2dcf1bSAndreas Gohr { 292*cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) return false; 293*cf2dcf1bSAndreas Gohr return file_exists($this->getInstallDir() . '/.git'); 294*cf2dcf1bSAndreas Gohr } 295*cf2dcf1bSAndreas Gohr 296*cf2dcf1bSAndreas Gohr /** 297*cf2dcf1bSAndreas Gohr * If the extension is bundled 298*cf2dcf1bSAndreas Gohr * 299*cf2dcf1bSAndreas Gohr * @return bool If the extension is bundled 300*cf2dcf1bSAndreas Gohr */ 301*cf2dcf1bSAndreas Gohr public function isBundled() 302*cf2dcf1bSAndreas Gohr { 303*cf2dcf1bSAndreas Gohr $this->loadRemoteInfo(); 304*cf2dcf1bSAndreas Gohr return $this->remoteInfo['bundled'] ?? in_array( 305*cf2dcf1bSAndreas Gohr $this->getId(), 306*cf2dcf1bSAndreas Gohr [ 307*cf2dcf1bSAndreas Gohr 'authad', 308*cf2dcf1bSAndreas Gohr 'authldap', 309*cf2dcf1bSAndreas Gohr 'authpdo', 310*cf2dcf1bSAndreas Gohr 'authplain', 311*cf2dcf1bSAndreas Gohr 'acl', 312*cf2dcf1bSAndreas Gohr 'config', 313*cf2dcf1bSAndreas Gohr 'extension', 314*cf2dcf1bSAndreas Gohr 'info', 315*cf2dcf1bSAndreas Gohr 'popularity', 316*cf2dcf1bSAndreas Gohr 'revert', 317*cf2dcf1bSAndreas Gohr 'safefnrecode', 318*cf2dcf1bSAndreas Gohr 'styling', 319*cf2dcf1bSAndreas Gohr 'testing', 320*cf2dcf1bSAndreas Gohr 'usermanager', 321*cf2dcf1bSAndreas Gohr 'logviewer', 322*cf2dcf1bSAndreas Gohr 'template:dokuwiki' 323*cf2dcf1bSAndreas Gohr ] 324*cf2dcf1bSAndreas Gohr ); 325*cf2dcf1bSAndreas Gohr } 326*cf2dcf1bSAndreas Gohr 327*cf2dcf1bSAndreas Gohr /** 328*cf2dcf1bSAndreas Gohr * Is the extension protected against any modification (disable/uninstall) 329*cf2dcf1bSAndreas Gohr * 330*cf2dcf1bSAndreas Gohr * @return bool if the extension is protected 331*cf2dcf1bSAndreas Gohr */ 332*cf2dcf1bSAndreas Gohr public function isProtected() 333*cf2dcf1bSAndreas Gohr { 334*cf2dcf1bSAndreas Gohr // never allow deinstalling the current auth plugin: 335*cf2dcf1bSAndreas Gohr global $conf; 336*cf2dcf1bSAndreas Gohr if ($this->getId() == $conf['authtype']) return true; 337*cf2dcf1bSAndreas Gohr 338*cf2dcf1bSAndreas Gohr // FIXME disallow current template to be uninstalled 339*cf2dcf1bSAndreas Gohr 340*cf2dcf1bSAndreas Gohr /** @var PluginController $plugin_controller */ 341*cf2dcf1bSAndreas Gohr global $plugin_controller; 342*cf2dcf1bSAndreas Gohr $cascade = $plugin_controller->getCascade(); 343*cf2dcf1bSAndreas Gohr return ($cascade['protected'][$this->getId()] ?? false); 344*cf2dcf1bSAndreas Gohr } 345*cf2dcf1bSAndreas Gohr 346*cf2dcf1bSAndreas Gohr /** 347*cf2dcf1bSAndreas Gohr * Is the extension installed in the correct directory? 348*cf2dcf1bSAndreas Gohr * 349*cf2dcf1bSAndreas Gohr * @return bool 350*cf2dcf1bSAndreas Gohr */ 351*cf2dcf1bSAndreas Gohr public function isInWrongFolder() 352*cf2dcf1bSAndreas Gohr { 353*cf2dcf1bSAndreas Gohr return $this->getInstallDir() != $this->currentDir; 354*cf2dcf1bSAndreas Gohr } 355*cf2dcf1bSAndreas Gohr 356*cf2dcf1bSAndreas Gohr /** 357*cf2dcf1bSAndreas Gohr * Is the extension enabled? 358*cf2dcf1bSAndreas Gohr * 359*cf2dcf1bSAndreas Gohr * @return bool 360*cf2dcf1bSAndreas Gohr */ 361*cf2dcf1bSAndreas Gohr public function isEnabled() 362*cf2dcf1bSAndreas Gohr { 363*cf2dcf1bSAndreas Gohr global $conf; 364*cf2dcf1bSAndreas Gohr if ($this->isTemplate()) { 365*cf2dcf1bSAndreas Gohr return ($conf['template'] == $this->getBase()); 366*cf2dcf1bSAndreas Gohr } 367*cf2dcf1bSAndreas Gohr 368*cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 369*cf2dcf1bSAndreas Gohr global $plugin_controller; 370*cf2dcf1bSAndreas Gohr return $plugin_controller->isEnabled($this->base); 371*cf2dcf1bSAndreas Gohr } 372*cf2dcf1bSAndreas Gohr 373*cf2dcf1bSAndreas Gohr // endregion 374*cf2dcf1bSAndreas Gohr 375*cf2dcf1bSAndreas Gohr // region Actions 376*cf2dcf1bSAndreas Gohr 377*cf2dcf1bSAndreas Gohr /** 378*cf2dcf1bSAndreas Gohr * Install or update the extension 379*cf2dcf1bSAndreas Gohr * 380*cf2dcf1bSAndreas Gohr * @throws Exception 381*cf2dcf1bSAndreas Gohr */ 382*cf2dcf1bSAndreas Gohr public function installOrUpdate() 383*cf2dcf1bSAndreas Gohr { 384*cf2dcf1bSAndreas Gohr $installer = new Installer(true); 385*cf2dcf1bSAndreas Gohr $installer->installFromUrl( 386*cf2dcf1bSAndreas Gohr $this->getURL(), 387*cf2dcf1bSAndreas Gohr $this->getBase(), 388*cf2dcf1bSAndreas Gohr ); 389*cf2dcf1bSAndreas Gohr } 390*cf2dcf1bSAndreas Gohr 391*cf2dcf1bSAndreas Gohr /** 392*cf2dcf1bSAndreas Gohr * Uninstall the extension 393*cf2dcf1bSAndreas Gohr * @throws Exception 394*cf2dcf1bSAndreas Gohr */ 395*cf2dcf1bSAndreas Gohr public function uninstall() 396*cf2dcf1bSAndreas Gohr { 397*cf2dcf1bSAndreas Gohr $installer = new Installer(true); 398*cf2dcf1bSAndreas Gohr $installer->uninstall($this); 399*cf2dcf1bSAndreas Gohr } 400*cf2dcf1bSAndreas Gohr 401*cf2dcf1bSAndreas Gohr /** 402*cf2dcf1bSAndreas Gohr * Enable the extension 403*cf2dcf1bSAndreas Gohr * @todo I'm unsure if this code should be here or part of Installer 404*cf2dcf1bSAndreas Gohr * @throws Exception 405*cf2dcf1bSAndreas Gohr */ 406*cf2dcf1bSAndreas Gohr public function enable() 407*cf2dcf1bSAndreas Gohr { 408*cf2dcf1bSAndreas Gohr if ($this->isTemplate()) throw new Exception('notimplemented'); 409*cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) throw new Exception('notinstalled'); 410*cf2dcf1bSAndreas Gohr if ($this->isEnabled()) throw new Exception('alreadyenabled'); 411*cf2dcf1bSAndreas Gohr 412*cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 413*cf2dcf1bSAndreas Gohr global $plugin_controller; 414*cf2dcf1bSAndreas Gohr if (!$plugin_controller->enable($this->base)) { 415*cf2dcf1bSAndreas Gohr throw new Exception('pluginlistsaveerror'); 416*cf2dcf1bSAndreas Gohr } 417*cf2dcf1bSAndreas Gohr Installer::purgeCache(); 418*cf2dcf1bSAndreas Gohr } 419*cf2dcf1bSAndreas Gohr 420*cf2dcf1bSAndreas Gohr /** 421*cf2dcf1bSAndreas Gohr * Disable the extension 422*cf2dcf1bSAndreas Gohr * @todo I'm unsure if this code should be here or part of Installer 423*cf2dcf1bSAndreas Gohr * @throws Exception 424*cf2dcf1bSAndreas Gohr */ 425*cf2dcf1bSAndreas Gohr public function disable() 426*cf2dcf1bSAndreas Gohr { 427*cf2dcf1bSAndreas Gohr if ($this->isTemplate()) throw new Exception('notimplemented'); 428*cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) throw new Exception('notinstalled'); 429*cf2dcf1bSAndreas Gohr if (!$this->isEnabled()) throw new Exception('alreadydisabled'); 430*cf2dcf1bSAndreas Gohr if ($this->isProtected()) throw new Exception('error_disable_protected'); 431*cf2dcf1bSAndreas Gohr 432*cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 433*cf2dcf1bSAndreas Gohr global $plugin_controller; 434*cf2dcf1bSAndreas Gohr if (!$plugin_controller->disable($this->base)) { 435*cf2dcf1bSAndreas Gohr throw new Exception('pluginlistsaveerror'); 436*cf2dcf1bSAndreas Gohr } 437*cf2dcf1bSAndreas Gohr Installer::purgeCache(); 438*cf2dcf1bSAndreas Gohr } 439*cf2dcf1bSAndreas Gohr 440*cf2dcf1bSAndreas Gohr // endregion 441*cf2dcf1bSAndreas Gohr 442*cf2dcf1bSAndreas Gohr // region Meta Data Management 443*cf2dcf1bSAndreas Gohr 444*cf2dcf1bSAndreas Gohr /** 445*cf2dcf1bSAndreas Gohr * This updates the timestamp and URL in the manager.dat file 446*cf2dcf1bSAndreas Gohr * 447*cf2dcf1bSAndreas Gohr * It is called by Installer when installing or updating an extension 448*cf2dcf1bSAndreas Gohr * 449*cf2dcf1bSAndreas Gohr * @param $url 450*cf2dcf1bSAndreas Gohr */ 451*cf2dcf1bSAndreas Gohr public function updateManagerInfo($url) 452*cf2dcf1bSAndreas Gohr { 453*cf2dcf1bSAndreas Gohr $this->managerInfo['downloadurl'] = $url; 454*cf2dcf1bSAndreas Gohr if (isset($this->managerInfo['installed'])) { 455*cf2dcf1bSAndreas Gohr // it's an update 456*cf2dcf1bSAndreas Gohr $this->managerInfo['updated'] = date('r'); 457*cf2dcf1bSAndreas Gohr } else { 458*cf2dcf1bSAndreas Gohr // it's a new install 459*cf2dcf1bSAndreas Gohr $this->managerInfo['installed'] = date('r'); 460*cf2dcf1bSAndreas Gohr } 461*cf2dcf1bSAndreas Gohr 462*cf2dcf1bSAndreas Gohr $managerpath = $this->getInstallDir() . '/manager.dat'; 463*cf2dcf1bSAndreas Gohr $data = ''; 464*cf2dcf1bSAndreas Gohr foreach ($this->managerInfo as $k => $v) { 465*cf2dcf1bSAndreas Gohr $data .= $k . '=' . $v . DOKU_LF; 466*cf2dcf1bSAndreas Gohr } 467*cf2dcf1bSAndreas Gohr io_saveFile($managerpath, $data); 468*cf2dcf1bSAndreas Gohr } 469*cf2dcf1bSAndreas Gohr 470*cf2dcf1bSAndreas Gohr /** 471*cf2dcf1bSAndreas Gohr * Reads the manager.dat file and fills the managerInfo array 472*cf2dcf1bSAndreas Gohr */ 473*cf2dcf1bSAndreas Gohr protected function readManagerInfo() 474*cf2dcf1bSAndreas Gohr { 475*cf2dcf1bSAndreas Gohr if ($this->managerInfo) return; 476*cf2dcf1bSAndreas Gohr 477*cf2dcf1bSAndreas Gohr $managerpath = $this->getInstallDir() . '/manager.dat'; 478*cf2dcf1bSAndreas Gohr if (!is_readable($managerpath)) return; 479*cf2dcf1bSAndreas Gohr 480*cf2dcf1bSAndreas Gohr $file = (array)@file($managerpath); 481*cf2dcf1bSAndreas Gohr foreach ($file as $line) { 482*cf2dcf1bSAndreas Gohr [$key, $value] = sexplode('=', $line, 2, ''); 483*cf2dcf1bSAndreas Gohr $key = trim($key); 484*cf2dcf1bSAndreas Gohr $value = trim($value); 485*cf2dcf1bSAndreas Gohr // backwards compatible with old plugin manager 486*cf2dcf1bSAndreas Gohr if ($key == 'url') $key = 'downloadurl'; 487*cf2dcf1bSAndreas Gohr $this->managerInfo[$key] = $value; 488*cf2dcf1bSAndreas Gohr } 489*cf2dcf1bSAndreas Gohr } 490*cf2dcf1bSAndreas Gohr 491*cf2dcf1bSAndreas Gohr /** 492*cf2dcf1bSAndreas Gohr * Reads the info file of the extension if available and fills the localInfo array 493*cf2dcf1bSAndreas Gohr */ 494*cf2dcf1bSAndreas Gohr protected function readLocalInfo() 495*cf2dcf1bSAndreas Gohr { 496*cf2dcf1bSAndreas Gohr if (!$this->currentDir) return; 497*cf2dcf1bSAndreas Gohr $file = $this->currentDir . '/' . $this->type . '.info.txt'; 498*cf2dcf1bSAndreas Gohr if (!is_readable($file)) return; 499*cf2dcf1bSAndreas Gohr $this->localInfo = confToHash($file, true); 500*cf2dcf1bSAndreas Gohr $this->localInfo = array_filter($this->localInfo); // remove all falsy keys 501*cf2dcf1bSAndreas Gohr } 502*cf2dcf1bSAndreas Gohr 503*cf2dcf1bSAndreas Gohr /** 504*cf2dcf1bSAndreas Gohr * Fetches the remote info from the repository 505*cf2dcf1bSAndreas Gohr * 506*cf2dcf1bSAndreas Gohr * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case 507*cf2dcf1bSAndreas Gohr */ 508*cf2dcf1bSAndreas Gohr protected function loadRemoteInfo() 509*cf2dcf1bSAndreas Gohr { 510*cf2dcf1bSAndreas Gohr if ($this->remoteInfo) return; 511*cf2dcf1bSAndreas Gohr $remote = Repository::getInstance(); 512*cf2dcf1bSAndreas Gohr try { 513*cf2dcf1bSAndreas Gohr $this->remoteInfo = (array)$remote->getExtensionData($this->getId()); 514*cf2dcf1bSAndreas Gohr } catch (Exception $e) { 515*cf2dcf1bSAndreas Gohr $this->remoteInfo = []; 516*cf2dcf1bSAndreas Gohr } 517*cf2dcf1bSAndreas Gohr } 518*cf2dcf1bSAndreas Gohr 519*cf2dcf1bSAndreas Gohr /** 520*cf2dcf1bSAndreas Gohr * Read information from either local or remote info 521*cf2dcf1bSAndreas Gohr * 522*cf2dcf1bSAndreas Gohr * Always prefers local info over remote info 523*cf2dcf1bSAndreas Gohr * 524*cf2dcf1bSAndreas Gohr * @param string|string[] $tag one or multiple keys to check 525*cf2dcf1bSAndreas Gohr * @param mixed $default 526*cf2dcf1bSAndreas Gohr * @return mixed 527*cf2dcf1bSAndreas Gohr */ 528*cf2dcf1bSAndreas Gohr protected function getTag($tag, $default = '') 529*cf2dcf1bSAndreas Gohr { 530*cf2dcf1bSAndreas Gohr foreach ((array)$tag as $t) { 531*cf2dcf1bSAndreas Gohr if (isset($this->localInfo[$t])) return $this->localInfo[$t]; 532*cf2dcf1bSAndreas Gohr } 533*cf2dcf1bSAndreas Gohr $this->loadRemoteInfo(); 534*cf2dcf1bSAndreas Gohr foreach ((array)$tag as $t) { 535*cf2dcf1bSAndreas Gohr if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t]; 536*cf2dcf1bSAndreas Gohr } 537*cf2dcf1bSAndreas Gohr 538*cf2dcf1bSAndreas Gohr return $default; 539*cf2dcf1bSAndreas Gohr } 540*cf2dcf1bSAndreas Gohr 541*cf2dcf1bSAndreas Gohr // endregion 542*cf2dcf1bSAndreas Gohr} 543