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{ 117c184cfcSAndreas Gohr public const TYPE_PLUGIN = 'plugin'; 127c184cfcSAndreas Gohr public const TYPE_TEMPLATE = 'template'; 13cf2dcf1bSAndreas Gohr 143e63733dSAndreas Gohr /** @var string[] The types the API uses for plugin components */ 153e63733dSAndreas Gohr public const COMPONENT_TYPES = [ 163e63733dSAndreas Gohr 1 => 'Syntax', 173e63733dSAndreas Gohr 2 => 'Admin', 183e63733dSAndreas Gohr 4 => 'Action', 193e63733dSAndreas Gohr 8 => 'Render', 203e63733dSAndreas Gohr 16 => 'Helper', 213e63733dSAndreas Gohr 32 => 'Template', 223e63733dSAndreas Gohr 64 => 'Remote', 233e63733dSAndreas Gohr 128 => 'Auth', 243e63733dSAndreas Gohr 256 => 'CLI', 253e63733dSAndreas Gohr 512 => 'CSS/JS-only', 263e63733dSAndreas Gohr ]; 273e63733dSAndreas Gohr 28*a1ef4d62SAndreas Gohr /** @var string[] List of plugin component file base names */ 29*a1ef4d62SAndreas Gohr public const COMPONENT_FILES = [ 30*a1ef4d62SAndreas Gohr 'syntax', 31*a1ef4d62SAndreas Gohr 'admin', 32*a1ef4d62SAndreas Gohr 'action', 33*a1ef4d62SAndreas Gohr 'render', 34*a1ef4d62SAndreas Gohr 'helper', 35*a1ef4d62SAndreas Gohr 'remote', 36*a1ef4d62SAndreas Gohr 'auth', 37*a1ef4d62SAndreas Gohr 'cli', 38*a1ef4d62SAndreas Gohr ]; 39*a1ef4d62SAndreas Gohr 40cf2dcf1bSAndreas Gohr /** @var string "plugin"|"template" */ 41cf2dcf1bSAndreas Gohr protected string $type = self::TYPE_PLUGIN; 42cf2dcf1bSAndreas Gohr 43cf2dcf1bSAndreas Gohr /** @var string The base name of this extension */ 44cf2dcf1bSAndreas Gohr protected string $base; 45cf2dcf1bSAndreas Gohr 4625d28a01SAndreas Gohr /** @var string The current location of this extension */ 4725d28a01SAndreas Gohr protected string $currentDir = ''; 48cf2dcf1bSAndreas Gohr 49cf2dcf1bSAndreas Gohr /** @var array The local info array of the extension */ 50cf2dcf1bSAndreas Gohr protected array $localInfo = []; 51cf2dcf1bSAndreas Gohr 52cf2dcf1bSAndreas Gohr /** @var array The remote info array of the extension */ 53cf2dcf1bSAndreas Gohr protected array $remoteInfo = []; 54cf2dcf1bSAndreas Gohr 557c9966a5SAndreas Gohr /** @var Manager|null The manager for this extension */ 5625d28a01SAndreas Gohr protected ?Manager $manager = null; 57cf2dcf1bSAndreas Gohr 58cf2dcf1bSAndreas Gohr // region Constructors 59cf2dcf1bSAndreas Gohr 60cf2dcf1bSAndreas Gohr /** 61cf2dcf1bSAndreas Gohr * The main constructor is private to force the use of the factory methods 62cf2dcf1bSAndreas Gohr */ 63cf2dcf1bSAndreas Gohr protected function __construct() 64cf2dcf1bSAndreas Gohr { 65cf2dcf1bSAndreas Gohr } 66cf2dcf1bSAndreas Gohr 67cf2dcf1bSAndreas Gohr /** 68a1e045f7SAndreas Gohr * Initializes an extension from an id 69a1e045f7SAndreas Gohr * 70a1e045f7SAndreas Gohr * @param string $id The id of the extension 71a1e045f7SAndreas Gohr * @return Extension 72a1e045f7SAndreas Gohr */ 73a1e045f7SAndreas Gohr public static function createFromId($id) 74a1e045f7SAndreas Gohr { 75a1e045f7SAndreas Gohr $extension = new self(); 76a1e045f7SAndreas Gohr $extension->initFromId($id); 77a1e045f7SAndreas Gohr return $extension; 78a1e045f7SAndreas Gohr } 79a1e045f7SAndreas Gohr 80a1e045f7SAndreas Gohr protected function initFromId($id) 81a1e045f7SAndreas Gohr { 82a1e045f7SAndreas Gohr [$type, $base] = $this->idToTypeBase($id); 83a1e045f7SAndreas Gohr $this->type = $type; 84a1e045f7SAndreas Gohr $this->base = $base; 85a1e045f7SAndreas Gohr $this->readLocalInfo(); 86a1e045f7SAndreas Gohr } 87a1e045f7SAndreas Gohr 88a1e045f7SAndreas Gohr /** 89cf2dcf1bSAndreas Gohr * Initializes an extension from a directory 90cf2dcf1bSAndreas Gohr * 91cf2dcf1bSAndreas Gohr * The given directory might be the one where the extension has already been installed to 92cf2dcf1bSAndreas Gohr * or it might be the extracted source in some temporary directory. 93cf2dcf1bSAndreas Gohr * 94cf2dcf1bSAndreas Gohr * @param string $dir Where the extension code is currently located 95cf2dcf1bSAndreas Gohr * @param string|null $type TYPE_PLUGIN|TYPE_TEMPLATE, null for auto-detection 96cf2dcf1bSAndreas Gohr * @param string $base The base name of the extension, null for auto-detection 97cf2dcf1bSAndreas Gohr * @return Extension 98cf2dcf1bSAndreas Gohr */ 99cf2dcf1bSAndreas Gohr public static function createFromDirectory($dir, $type = null, $base = null) 100cf2dcf1bSAndreas Gohr { 101cf2dcf1bSAndreas Gohr $extension = new self(); 102cf2dcf1bSAndreas Gohr $extension->initFromDirectory($dir, $type, $base); 103cf2dcf1bSAndreas Gohr return $extension; 104cf2dcf1bSAndreas Gohr } 105cf2dcf1bSAndreas Gohr 106cf2dcf1bSAndreas Gohr protected function initFromDirectory($dir, $type = null, $base = null) 107cf2dcf1bSAndreas Gohr { 108cf2dcf1bSAndreas Gohr if (!is_dir($dir)) throw new RuntimeException('Directory not found: ' . $dir); 109e8fd67e9SAndreas Gohr $this->currentDir = fullpath($dir); 110cf2dcf1bSAndreas Gohr 111cf2dcf1bSAndreas Gohr if ($type === null || $type === self::TYPE_TEMPLATE) { 112cf2dcf1bSAndreas Gohr if ( 1138fe483c9SAndreas Gohr file_exists($dir . '/template.info.txt') || 114cf2dcf1bSAndreas Gohr file_exists($dir . '/style.ini') || 115cf2dcf1bSAndreas Gohr file_exists($dir . '/main.php') || 116cf2dcf1bSAndreas Gohr file_exists($dir . '/detail.php') || 117cf2dcf1bSAndreas Gohr file_exists($dir . '/mediamanager.php') 118cf2dcf1bSAndreas Gohr ) { 119cf2dcf1bSAndreas Gohr $this->type = self::TYPE_TEMPLATE; 120cf2dcf1bSAndreas Gohr } 121cf2dcf1bSAndreas Gohr } else { 122cf2dcf1bSAndreas Gohr $this->type = self::TYPE_PLUGIN; 123cf2dcf1bSAndreas Gohr } 124cf2dcf1bSAndreas Gohr 125cf2dcf1bSAndreas Gohr $this->readLocalInfo(); 126cf2dcf1bSAndreas Gohr 127cf2dcf1bSAndreas Gohr if ($base !== null) { 128cf2dcf1bSAndreas Gohr $this->base = $base; 129cf2dcf1bSAndreas Gohr } elseif (isset($this->localInfo['base'])) { 130cf2dcf1bSAndreas Gohr $this->base = $this->localInfo['base']; 131cf2dcf1bSAndreas Gohr } else { 132*a1ef4d62SAndreas Gohr $this->base = $this->getBaseFromClass($dir) ?: basename($dir); 133cf2dcf1bSAndreas Gohr } 134cf2dcf1bSAndreas Gohr } 135cf2dcf1bSAndreas Gohr 136cf2dcf1bSAndreas Gohr /** 137cf2dcf1bSAndreas Gohr * Initializes an extension from remote data 138cf2dcf1bSAndreas Gohr * 139cf2dcf1bSAndreas Gohr * @param array $data The data as returned by the repository api 140cf2dcf1bSAndreas Gohr * @return Extension 141cf2dcf1bSAndreas Gohr */ 142cf2dcf1bSAndreas Gohr public static function createFromRemoteData($data) 143cf2dcf1bSAndreas Gohr { 144cf2dcf1bSAndreas Gohr $extension = new self(); 145cf2dcf1bSAndreas Gohr $extension->initFromRemoteData($data); 146cf2dcf1bSAndreas Gohr return $extension; 147cf2dcf1bSAndreas Gohr } 148cf2dcf1bSAndreas Gohr 149cf2dcf1bSAndreas Gohr protected function initFromRemoteData($data) 150cf2dcf1bSAndreas Gohr { 151cf2dcf1bSAndreas Gohr if (!isset($data['plugin'])) throw new RuntimeException('Invalid remote data'); 152cf2dcf1bSAndreas Gohr 153a1e045f7SAndreas Gohr [$type, $base] = $this->idToTypeBase($data['plugin']); 154cf2dcf1bSAndreas Gohr $this->remoteInfo = $data; 155cf2dcf1bSAndreas Gohr $this->type = $type; 156cf2dcf1bSAndreas Gohr $this->base = $base; 157cf2dcf1bSAndreas Gohr 158cf2dcf1bSAndreas Gohr if ($this->isInstalled()) { 159cf2dcf1bSAndreas Gohr $this->currentDir = $this->getInstallDir(); 160cf2dcf1bSAndreas Gohr $this->readLocalInfo(); 161cf2dcf1bSAndreas Gohr } 162cf2dcf1bSAndreas Gohr } 163cf2dcf1bSAndreas Gohr 164cf2dcf1bSAndreas Gohr // endregion 165cf2dcf1bSAndreas Gohr 166cf2dcf1bSAndreas Gohr // region Getters 167cf2dcf1bSAndreas Gohr 168cf2dcf1bSAndreas Gohr /** 1694fd6a1d7SAndreas Gohr * @param bool $wrap If true, the id is wrapped in backticks 170cf2dcf1bSAndreas Gohr * @return string The extension id (same as base but prefixed with "template:" for templates) 171cf2dcf1bSAndreas Gohr */ 1724fd6a1d7SAndreas Gohr public function getId($wrap = false) 173cf2dcf1bSAndreas Gohr { 174cf2dcf1bSAndreas Gohr if ($this->type === self::TYPE_TEMPLATE) { 1754fd6a1d7SAndreas Gohr $id = self::TYPE_TEMPLATE . ':' . $this->base; 1764fd6a1d7SAndreas Gohr } else { 1774fd6a1d7SAndreas Gohr $id = $this->base; 178cf2dcf1bSAndreas Gohr } 1794fd6a1d7SAndreas Gohr if ($wrap) $id = "`$id`"; 1804fd6a1d7SAndreas Gohr return $id; 181cf2dcf1bSAndreas Gohr } 182cf2dcf1bSAndreas Gohr 183cf2dcf1bSAndreas Gohr /** 184cf2dcf1bSAndreas Gohr * Get the base name of this extension 185cf2dcf1bSAndreas Gohr * 186cf2dcf1bSAndreas Gohr * @return string 187cf2dcf1bSAndreas Gohr */ 188cf2dcf1bSAndreas Gohr public function getBase() 189cf2dcf1bSAndreas Gohr { 190cf2dcf1bSAndreas Gohr return $this->base; 191cf2dcf1bSAndreas Gohr } 192cf2dcf1bSAndreas Gohr 193cf2dcf1bSAndreas Gohr /** 194cf2dcf1bSAndreas Gohr * Get the type of the extension 195cf2dcf1bSAndreas Gohr * 196cf2dcf1bSAndreas Gohr * @return string "plugin"|"template" 197cf2dcf1bSAndreas Gohr */ 198cf2dcf1bSAndreas Gohr public function getType() 199cf2dcf1bSAndreas Gohr { 200cf2dcf1bSAndreas Gohr return $this->type; 201cf2dcf1bSAndreas Gohr } 202cf2dcf1bSAndreas Gohr 203cf2dcf1bSAndreas Gohr /** 204cf2dcf1bSAndreas Gohr * The current directory of the extension 205cf2dcf1bSAndreas Gohr * 206cf2dcf1bSAndreas Gohr * @return string|null 207cf2dcf1bSAndreas Gohr */ 208cf2dcf1bSAndreas Gohr public function getCurrentDir() 209cf2dcf1bSAndreas Gohr { 210cf2dcf1bSAndreas Gohr // recheck that the current currentDir is still valid 211cf2dcf1bSAndreas Gohr if ($this->currentDir && !is_dir($this->currentDir)) { 21225d28a01SAndreas Gohr $this->currentDir = ''; 213cf2dcf1bSAndreas Gohr } 214cf2dcf1bSAndreas Gohr 215cf2dcf1bSAndreas Gohr // if the extension is installed, then the currentDir is the install dir! 216cf2dcf1bSAndreas Gohr if (!$this->currentDir && $this->isInstalled()) { 217cf2dcf1bSAndreas Gohr $this->currentDir = $this->getInstallDir(); 218cf2dcf1bSAndreas Gohr } 219cf2dcf1bSAndreas Gohr 220cf2dcf1bSAndreas Gohr return $this->currentDir; 221cf2dcf1bSAndreas Gohr } 222cf2dcf1bSAndreas Gohr 223cf2dcf1bSAndreas Gohr /** 224cf2dcf1bSAndreas Gohr * Get the directory where this extension should be installed in 225cf2dcf1bSAndreas Gohr * 226cf2dcf1bSAndreas Gohr * Note: this does not mean that the extension is actually installed there 227cf2dcf1bSAndreas Gohr * 228cf2dcf1bSAndreas Gohr * @return string 229cf2dcf1bSAndreas Gohr */ 230cf2dcf1bSAndreas Gohr public function getInstallDir() 231cf2dcf1bSAndreas Gohr { 232cf2dcf1bSAndreas Gohr if ($this->isTemplate()) { 233176901c2SAndreas Gohr $dir = dirname(tpl_incdir()) . '/' . $this->base; 234cf2dcf1bSAndreas Gohr } else { 235cf2dcf1bSAndreas Gohr $dir = DOKU_PLUGIN . $this->base; 236cf2dcf1bSAndreas Gohr } 237cf2dcf1bSAndreas Gohr 23825d28a01SAndreas Gohr return fullpath($dir); 239cf2dcf1bSAndreas Gohr } 240cf2dcf1bSAndreas Gohr 241cf2dcf1bSAndreas Gohr 242cf2dcf1bSAndreas Gohr /** 243cf2dcf1bSAndreas Gohr * Get the display name of the extension 244cf2dcf1bSAndreas Gohr * 245cf2dcf1bSAndreas Gohr * @return string 246cf2dcf1bSAndreas Gohr */ 247cf2dcf1bSAndreas Gohr public function getDisplayName() 248cf2dcf1bSAndreas Gohr { 249cf2dcf1bSAndreas Gohr return $this->getTag('name', PhpString::ucwords($this->getBase() . ' ' . $this->getType())); 250cf2dcf1bSAndreas Gohr } 251cf2dcf1bSAndreas Gohr 252cf2dcf1bSAndreas Gohr /** 253cf2dcf1bSAndreas Gohr * Get the author name of the extension 254cf2dcf1bSAndreas Gohr * 255cf2dcf1bSAndreas Gohr * @return string Returns an empty string if the author info is missing 256cf2dcf1bSAndreas Gohr */ 257cf2dcf1bSAndreas Gohr public function getAuthor() 258cf2dcf1bSAndreas Gohr { 259cf2dcf1bSAndreas Gohr return $this->getTag('author'); 260cf2dcf1bSAndreas Gohr } 261cf2dcf1bSAndreas Gohr 262cf2dcf1bSAndreas Gohr /** 263cf2dcf1bSAndreas Gohr * Get the email of the author of the extension if there is any 264cf2dcf1bSAndreas Gohr * 265cf2dcf1bSAndreas Gohr * @return string Returns an empty string if the email info is missing 266cf2dcf1bSAndreas Gohr */ 267cf2dcf1bSAndreas Gohr public function getEmail() 268cf2dcf1bSAndreas Gohr { 269cf2dcf1bSAndreas Gohr // email is only in the local data 270cf2dcf1bSAndreas Gohr return $this->localInfo['email'] ?? ''; 271cf2dcf1bSAndreas Gohr } 272cf2dcf1bSAndreas Gohr 273cf2dcf1bSAndreas Gohr /** 274cf2dcf1bSAndreas Gohr * Get the email id, i.e. the md5sum of the email 275cf2dcf1bSAndreas Gohr * 276cf2dcf1bSAndreas Gohr * @return string Empty string if no email is available 277cf2dcf1bSAndreas Gohr */ 278cf2dcf1bSAndreas Gohr public function getEmailID() 279cf2dcf1bSAndreas Gohr { 280cf2dcf1bSAndreas Gohr if (!empty($this->remoteInfo['emailid'])) return $this->remoteInfo['emailid']; 281cf2dcf1bSAndreas Gohr if (!empty($this->localInfo['email'])) return md5($this->localInfo['email']); 282cf2dcf1bSAndreas Gohr return ''; 283cf2dcf1bSAndreas Gohr } 284cf2dcf1bSAndreas Gohr 285cf2dcf1bSAndreas Gohr /** 286cf2dcf1bSAndreas Gohr * Get the description of the extension 287cf2dcf1bSAndreas Gohr * 288cf2dcf1bSAndreas Gohr * @return string Empty string if no description is available 289cf2dcf1bSAndreas Gohr */ 290cf2dcf1bSAndreas Gohr public function getDescription() 291cf2dcf1bSAndreas Gohr { 292cf2dcf1bSAndreas Gohr return $this->getTag(['desc', 'description']); 293cf2dcf1bSAndreas Gohr } 294cf2dcf1bSAndreas Gohr 295cf2dcf1bSAndreas Gohr /** 296cf2dcf1bSAndreas Gohr * Get the URL of the extension, usually a page on dokuwiki.org 297cf2dcf1bSAndreas Gohr * 298cf2dcf1bSAndreas Gohr * @return string 299cf2dcf1bSAndreas Gohr */ 300cf2dcf1bSAndreas Gohr public function getURL() 301cf2dcf1bSAndreas Gohr { 302cf2dcf1bSAndreas Gohr return $this->getTag( 303cf2dcf1bSAndreas Gohr 'url', 304cf2dcf1bSAndreas Gohr 'https://www.dokuwiki.org/' . 305cf2dcf1bSAndreas Gohr ($this->isTemplate() ? 'template' : 'plugin') . ':' . $this->getBase() 306cf2dcf1bSAndreas Gohr ); 307cf2dcf1bSAndreas Gohr } 308cf2dcf1bSAndreas Gohr 309cf2dcf1bSAndreas Gohr /** 3107c9966a5SAndreas Gohr * Get the version of the extension that is actually installed 3117c9966a5SAndreas Gohr * 3127c9966a5SAndreas Gohr * Returns an empty string if the version is not available 3137c9966a5SAndreas Gohr * 3147c9966a5SAndreas Gohr * @return string 3157c9966a5SAndreas Gohr */ 3167c9966a5SAndreas Gohr public function getInstalledVersion() 3177c9966a5SAndreas Gohr { 3187c9966a5SAndreas Gohr return $this->localInfo['date'] ?? ''; 3197c9966a5SAndreas Gohr } 3207c9966a5SAndreas Gohr 3217c9966a5SAndreas Gohr /** 3224fd6a1d7SAndreas Gohr * Get the types of components this extension provides 3234fd6a1d7SAndreas Gohr * 3244fd6a1d7SAndreas Gohr * @return array int -> type 3254fd6a1d7SAndreas Gohr */ 3264fd6a1d7SAndreas Gohr public function getComponentTypes() 3274fd6a1d7SAndreas Gohr { 3288fe483c9SAndreas Gohr // for installed extensions we can check the files 3298fe483c9SAndreas Gohr if ($this->isInstalled()) { 3308fe483c9SAndreas Gohr if ($this->isTemplate()) { 3318fe483c9SAndreas Gohr return ['Template']; 3328fe483c9SAndreas Gohr } else { 3338fe483c9SAndreas Gohr $types = []; 3343e63733dSAndreas Gohr foreach (self::COMPONENT_TYPES as $type) { 3358fe483c9SAndreas Gohr $check = strtolower($type); 3368fe483c9SAndreas Gohr if ( 3378fe483c9SAndreas Gohr file_exists($this->getInstallDir() . '/' . $check . '.php') || 3388fe483c9SAndreas Gohr is_dir($this->getInstallDir() . '/' . $check) 3398fe483c9SAndreas Gohr ) { 3408fe483c9SAndreas Gohr $types[] = $type; 3418fe483c9SAndreas Gohr } 3428fe483c9SAndreas Gohr } 3438fe483c9SAndreas Gohr return $types; 3448fe483c9SAndreas Gohr } 3458fe483c9SAndreas Gohr } 3468fe483c9SAndreas Gohr // still, here? use the remote info 3474fd6a1d7SAndreas Gohr return $this->getTag('types', []); 3484fd6a1d7SAndreas Gohr } 3494fd6a1d7SAndreas Gohr 3504fd6a1d7SAndreas Gohr /** 35125d28a01SAndreas Gohr * Get a list of extension ids this extension depends on 35225d28a01SAndreas Gohr * 35325d28a01SAndreas Gohr * @return string[] 35425d28a01SAndreas Gohr */ 35525d28a01SAndreas Gohr public function getDependencyList() 35625d28a01SAndreas Gohr { 35725d28a01SAndreas Gohr return $this->getTag('depends', []); 35825d28a01SAndreas Gohr } 35925d28a01SAndreas Gohr 36025d28a01SAndreas Gohr /** 361b69d74f1SAndreas Gohr * Get a list of extensions that are currently installed, enabled and depend on this extension 362b69d74f1SAndreas Gohr * 363b69d74f1SAndreas Gohr * @return Extension[] 364b69d74f1SAndreas Gohr */ 365b69d74f1SAndreas Gohr public function getDependants() 366b69d74f1SAndreas Gohr { 367b69d74f1SAndreas Gohr $local = new Local(); 368b69d74f1SAndreas Gohr $extensions = $local->getExtensions(); 369b69d74f1SAndreas Gohr $dependants = []; 370b69d74f1SAndreas Gohr foreach ($extensions as $extension) { 371b69d74f1SAndreas Gohr if ( 372b69d74f1SAndreas Gohr in_array($this->getId(), $extension->getDependencyList()) && 373b69d74f1SAndreas Gohr $extension->isEnabled() 374b69d74f1SAndreas Gohr ) { 375b69d74f1SAndreas Gohr $dependants[$extension->getId()] = $extension; 376b69d74f1SAndreas Gohr } 377b69d74f1SAndreas Gohr } 378b69d74f1SAndreas Gohr return $dependants; 379b69d74f1SAndreas Gohr } 380b69d74f1SAndreas Gohr 381b69d74f1SAndreas Gohr /** 382b2a05b76SAndreas Gohr * Return the minimum PHP version required by the extension 383b2a05b76SAndreas Gohr * 384b2a05b76SAndreas Gohr * Empty if not set 385b2a05b76SAndreas Gohr * 386b2a05b76SAndreas Gohr * @return string 387b2a05b76SAndreas Gohr */ 388b2a05b76SAndreas Gohr public function getMinimumPHPVersion() 389b2a05b76SAndreas Gohr { 390b2a05b76SAndreas Gohr return $this->getTag('phpmin', ''); 391b2a05b76SAndreas Gohr } 392b2a05b76SAndreas Gohr 393b2a05b76SAndreas Gohr /** 394b2a05b76SAndreas Gohr * Return the minimum PHP version supported by the extension 395b2a05b76SAndreas Gohr * 396b2a05b76SAndreas Gohr * @return string 397b2a05b76SAndreas Gohr */ 398b2a05b76SAndreas Gohr public function getMaximumPHPVersion() 399b2a05b76SAndreas Gohr { 400b2a05b76SAndreas Gohr return $this->getTag('phpmax', ''); 401b2a05b76SAndreas Gohr } 402b2a05b76SAndreas Gohr 403b2a05b76SAndreas Gohr /** 404cf2dcf1bSAndreas Gohr * Is this extension a template? 405cf2dcf1bSAndreas Gohr * 406cf2dcf1bSAndreas Gohr * @return bool false if it is a plugin 407cf2dcf1bSAndreas Gohr */ 408cf2dcf1bSAndreas Gohr public function isTemplate() 409cf2dcf1bSAndreas Gohr { 410cf2dcf1bSAndreas Gohr return $this->type === self::TYPE_TEMPLATE; 411cf2dcf1bSAndreas Gohr } 412cf2dcf1bSAndreas Gohr 413cf2dcf1bSAndreas Gohr /** 414cf2dcf1bSAndreas Gohr * Is the extension installed locally? 415cf2dcf1bSAndreas Gohr * 416cf2dcf1bSAndreas Gohr * @return bool 417cf2dcf1bSAndreas Gohr */ 418cf2dcf1bSAndreas Gohr public function isInstalled() 419cf2dcf1bSAndreas Gohr { 420cf2dcf1bSAndreas Gohr return is_dir($this->getInstallDir()); 421cf2dcf1bSAndreas Gohr } 422cf2dcf1bSAndreas Gohr 423cf2dcf1bSAndreas Gohr /** 424cf2dcf1bSAndreas Gohr * Is the extension under git control? 425cf2dcf1bSAndreas Gohr * 426cf2dcf1bSAndreas Gohr * @return bool 427cf2dcf1bSAndreas Gohr */ 428cf2dcf1bSAndreas Gohr public function isGitControlled() 429cf2dcf1bSAndreas Gohr { 430cf2dcf1bSAndreas Gohr if (!$this->isInstalled()) return false; 431cf2dcf1bSAndreas Gohr return file_exists($this->getInstallDir() . '/.git'); 432cf2dcf1bSAndreas Gohr } 433cf2dcf1bSAndreas Gohr 434cf2dcf1bSAndreas Gohr /** 435cf2dcf1bSAndreas Gohr * If the extension is bundled 436cf2dcf1bSAndreas Gohr * 437cf2dcf1bSAndreas Gohr * @return bool If the extension is bundled 438cf2dcf1bSAndreas Gohr */ 439cf2dcf1bSAndreas Gohr public function isBundled() 440cf2dcf1bSAndreas Gohr { 441cf2dcf1bSAndreas Gohr $this->loadRemoteInfo(); 442cf2dcf1bSAndreas Gohr return $this->remoteInfo['bundled'] ?? in_array( 443cf2dcf1bSAndreas Gohr $this->getId(), 444cf2dcf1bSAndreas Gohr [ 445cf2dcf1bSAndreas Gohr 'authad', 446cf2dcf1bSAndreas Gohr 'authldap', 447cf2dcf1bSAndreas Gohr 'authpdo', 448cf2dcf1bSAndreas Gohr 'authplain', 449cf2dcf1bSAndreas Gohr 'acl', 450cf2dcf1bSAndreas Gohr 'config', 451cf2dcf1bSAndreas Gohr 'extension', 452cf2dcf1bSAndreas Gohr 'info', 453cf2dcf1bSAndreas Gohr 'popularity', 454cf2dcf1bSAndreas Gohr 'revert', 455cf2dcf1bSAndreas Gohr 'safefnrecode', 456cf2dcf1bSAndreas Gohr 'styling', 457cf2dcf1bSAndreas Gohr 'testing', 458cf2dcf1bSAndreas Gohr 'usermanager', 459cf2dcf1bSAndreas Gohr 'logviewer', 460cf2dcf1bSAndreas Gohr 'template:dokuwiki' 461cf2dcf1bSAndreas Gohr ] 462cf2dcf1bSAndreas Gohr ); 463cf2dcf1bSAndreas Gohr } 464cf2dcf1bSAndreas Gohr 465cf2dcf1bSAndreas Gohr /** 466cf2dcf1bSAndreas Gohr * Is the extension protected against any modification (disable/uninstall) 467cf2dcf1bSAndreas Gohr * 468cf2dcf1bSAndreas Gohr * @return bool if the extension is protected 469cf2dcf1bSAndreas Gohr */ 470cf2dcf1bSAndreas Gohr public function isProtected() 471cf2dcf1bSAndreas Gohr { 472cf2dcf1bSAndreas Gohr // never allow deinstalling the current auth plugin: 473cf2dcf1bSAndreas Gohr global $conf; 474cf2dcf1bSAndreas Gohr if ($this->getId() == $conf['authtype']) return true; 475cf2dcf1bSAndreas Gohr 476077f55feSAndreas Gohr // disallow current template to be uninstalled 477077f55feSAndreas Gohr if ($this->isTemplate() && ($this->getBase() === $conf['template'])) return true; 478cf2dcf1bSAndreas Gohr 479cf2dcf1bSAndreas Gohr /** @var PluginController $plugin_controller */ 480cf2dcf1bSAndreas Gohr global $plugin_controller; 481cf2dcf1bSAndreas Gohr $cascade = $plugin_controller->getCascade(); 482cf2dcf1bSAndreas Gohr return ($cascade['protected'][$this->getId()] ?? false); 483cf2dcf1bSAndreas Gohr } 484cf2dcf1bSAndreas Gohr 485cf2dcf1bSAndreas Gohr /** 486cf2dcf1bSAndreas Gohr * Is the extension installed in the correct directory? 487cf2dcf1bSAndreas Gohr * 488cf2dcf1bSAndreas Gohr * @return bool 489cf2dcf1bSAndreas Gohr */ 490cf2dcf1bSAndreas Gohr public function isInWrongFolder() 491cf2dcf1bSAndreas Gohr { 4924fd6a1d7SAndreas Gohr if (!$this->isInstalled()) return false; 493cf2dcf1bSAndreas Gohr return $this->getInstallDir() != $this->currentDir; 494cf2dcf1bSAndreas Gohr } 495cf2dcf1bSAndreas Gohr 496cf2dcf1bSAndreas Gohr /** 497cf2dcf1bSAndreas Gohr * Is the extension enabled? 498cf2dcf1bSAndreas Gohr * 499cf2dcf1bSAndreas Gohr * @return bool 500cf2dcf1bSAndreas Gohr */ 501cf2dcf1bSAndreas Gohr public function isEnabled() 502cf2dcf1bSAndreas Gohr { 503cf2dcf1bSAndreas Gohr global $conf; 504cf2dcf1bSAndreas Gohr if ($this->isTemplate()) { 505cf2dcf1bSAndreas Gohr return ($conf['template'] == $this->getBase()); 506cf2dcf1bSAndreas Gohr } 507cf2dcf1bSAndreas Gohr 508cf2dcf1bSAndreas Gohr /* @var PluginController $plugin_controller */ 509cf2dcf1bSAndreas Gohr global $plugin_controller; 510cf2dcf1bSAndreas Gohr return $plugin_controller->isEnabled($this->base); 511cf2dcf1bSAndreas Gohr } 512cf2dcf1bSAndreas Gohr 513160d3688SAndreas Gohr /** 514160d3688SAndreas Gohr * Has the download URL changed since the last download? 515160d3688SAndreas Gohr * 516160d3688SAndreas Gohr * @return bool 517160d3688SAndreas Gohr */ 518160d3688SAndreas Gohr public function hasChangedURL() 519160d3688SAndreas Gohr { 5204fd6a1d7SAndreas Gohr $last = $this->getManager()->getDownloadURL(); 521160d3688SAndreas Gohr if (!$last) return false; 5222ee9c305Sfiwswe $url = $this->getDownloadURL(); 5232ee9c305Sfiwswe if (!$url) return false; 5242ee9c305Sfiwswe return $last !== $url; 525160d3688SAndreas Gohr } 526160d3688SAndreas Gohr 527160d3688SAndreas Gohr /** 528160d3688SAndreas Gohr * Is an update available for this extension? 529160d3688SAndreas Gohr * 530160d3688SAndreas Gohr * @return bool 531160d3688SAndreas Gohr */ 5324fd6a1d7SAndreas Gohr public function isUpdateAvailable() 533160d3688SAndreas Gohr { 534160d3688SAndreas Gohr if ($this->isBundled()) return false; // bundled extensions are never updated 535160d3688SAndreas Gohr $self = $this->getInstalledVersion(); 536160d3688SAndreas Gohr $remote = $this->getLastUpdate(); 537160d3688SAndreas Gohr return $self < $remote; 538160d3688SAndreas Gohr } 539160d3688SAndreas Gohr 540cf2dcf1bSAndreas Gohr // endregion 541cf2dcf1bSAndreas Gohr 5427c9966a5SAndreas Gohr // region Remote Info 5437c9966a5SAndreas Gohr 5447c9966a5SAndreas Gohr /** 5457c9966a5SAndreas Gohr * Get the date of the last available update 5467c9966a5SAndreas Gohr * 5477c9966a5SAndreas Gohr * @return string yyyy-mm-dd 5487c9966a5SAndreas Gohr */ 5497c9966a5SAndreas Gohr public function getLastUpdate() 5507c9966a5SAndreas Gohr { 5517c9966a5SAndreas Gohr return $this->getRemoteTag('lastupdate'); 5527c9966a5SAndreas Gohr } 5537c9966a5SAndreas Gohr 5547c9966a5SAndreas Gohr /** 5557c9966a5SAndreas Gohr * Get a list of tags this extension is tagged with at dokuwiki.org 5567c9966a5SAndreas Gohr * 5577c9966a5SAndreas Gohr * @return string[] 5587c9966a5SAndreas Gohr */ 5597c9966a5SAndreas Gohr public function getTags() 5607c9966a5SAndreas Gohr { 5617c9966a5SAndreas Gohr return $this->getRemoteTag('tags', []); 5627c9966a5SAndreas Gohr } 5637c9966a5SAndreas Gohr 5647c9966a5SAndreas Gohr /** 5657c9966a5SAndreas Gohr * Get the popularity of the extension 5667c9966a5SAndreas Gohr * 5677c9966a5SAndreas Gohr * This is a float between 0 and 1 5687c9966a5SAndreas Gohr * 5697c9966a5SAndreas Gohr * @return float 5707c9966a5SAndreas Gohr */ 5717c9966a5SAndreas Gohr public function getPopularity() 5727c9966a5SAndreas Gohr { 5737c9966a5SAndreas Gohr return (float)$this->getRemoteTag('popularity', 0); 5747c9966a5SAndreas Gohr } 5757c9966a5SAndreas Gohr 5767c9966a5SAndreas Gohr /** 5777c9966a5SAndreas Gohr * Get the text of the update message if there is any 5787c9966a5SAndreas Gohr * 5797c9966a5SAndreas Gohr * @return string 5807c9966a5SAndreas Gohr */ 5817c9966a5SAndreas Gohr public function getUpdateMessage() 5827c9966a5SAndreas Gohr { 5837c9966a5SAndreas Gohr return $this->getRemoteTag('updatemessage'); 5847c9966a5SAndreas Gohr } 5857c9966a5SAndreas Gohr 5867c9966a5SAndreas Gohr /** 5877c9966a5SAndreas Gohr * Get the text of the security warning if there is any 5887c9966a5SAndreas Gohr * 5897c9966a5SAndreas Gohr * @return string 5907c9966a5SAndreas Gohr */ 5917c9966a5SAndreas Gohr public function getSecurityWarning() 5927c9966a5SAndreas Gohr { 5937c9966a5SAndreas Gohr return $this->getRemoteTag('securitywarning'); 5947c9966a5SAndreas Gohr } 5957c9966a5SAndreas Gohr 5967c9966a5SAndreas Gohr /** 5977c9966a5SAndreas Gohr * Get the text of the security issue if there is any 5987c9966a5SAndreas Gohr * 5997c9966a5SAndreas Gohr * @return string 6007c9966a5SAndreas Gohr */ 6017c9966a5SAndreas Gohr public function getSecurityIssue() 6027c9966a5SAndreas Gohr { 6037c9966a5SAndreas Gohr return $this->getRemoteTag('securityissue'); 6047c9966a5SAndreas Gohr } 6057c9966a5SAndreas Gohr 6067c9966a5SAndreas Gohr /** 6077c9966a5SAndreas Gohr * Get the URL of the screenshot of the extension if there is any 6087c9966a5SAndreas Gohr * 6097c9966a5SAndreas Gohr * @return string 6107c9966a5SAndreas Gohr */ 6117c9966a5SAndreas Gohr public function getScreenshotURL() 6127c9966a5SAndreas Gohr { 6137c9966a5SAndreas Gohr return $this->getRemoteTag('screenshoturl'); 6147c9966a5SAndreas Gohr } 6157c9966a5SAndreas Gohr 6167c9966a5SAndreas Gohr /** 6177c9966a5SAndreas Gohr * Get the URL of the thumbnail of the extension if there is any 6187c9966a5SAndreas Gohr * 6197c9966a5SAndreas Gohr * @return string 6207c9966a5SAndreas Gohr */ 6217c9966a5SAndreas Gohr public function getThumbnailURL() 6227c9966a5SAndreas Gohr { 6237c9966a5SAndreas Gohr return $this->getRemoteTag('thumbnailurl'); 6247c9966a5SAndreas Gohr } 6257c9966a5SAndreas Gohr 6267c9966a5SAndreas Gohr /** 6277c9966a5SAndreas Gohr * Get the download URL of the extension if there is any 6287c9966a5SAndreas Gohr * 6297c9966a5SAndreas Gohr * @return string 6307c9966a5SAndreas Gohr */ 6317c9966a5SAndreas Gohr public function getDownloadURL() 6327c9966a5SAndreas Gohr { 6337c9966a5SAndreas Gohr return $this->getRemoteTag('downloadurl'); 6347c9966a5SAndreas Gohr } 6357c9966a5SAndreas Gohr 6367c9966a5SAndreas Gohr /** 6377c9966a5SAndreas Gohr * Get the bug tracker URL of the extension if there is any 6387c9966a5SAndreas Gohr * 6397c9966a5SAndreas Gohr * @return string 6407c9966a5SAndreas Gohr */ 6417c9966a5SAndreas Gohr public function getBugtrackerURL() 6427c9966a5SAndreas Gohr { 6437c9966a5SAndreas Gohr return $this->getRemoteTag('bugtracker'); 6447c9966a5SAndreas Gohr } 6457c9966a5SAndreas Gohr 6467c9966a5SAndreas Gohr /** 6477c9966a5SAndreas Gohr * Get the URL of the source repository if there is any 6487c9966a5SAndreas Gohr * 6497c9966a5SAndreas Gohr * @return string 6507c9966a5SAndreas Gohr */ 6517c9966a5SAndreas Gohr public function getSourcerepoURL() 6527c9966a5SAndreas Gohr { 6537c9966a5SAndreas Gohr return $this->getRemoteTag('sourcerepo'); 6547c9966a5SAndreas Gohr } 6557c9966a5SAndreas Gohr 6567c9966a5SAndreas Gohr /** 6577c9966a5SAndreas Gohr * Get the donation URL of the extension if there is any 6587c9966a5SAndreas Gohr * 6597c9966a5SAndreas Gohr * @return string 6607c9966a5SAndreas Gohr */ 6617c9966a5SAndreas Gohr public function getDonationURL() 6627c9966a5SAndreas Gohr { 6637c9966a5SAndreas Gohr return $this->getRemoteTag('donationurl'); 6647c9966a5SAndreas Gohr } 6657c9966a5SAndreas Gohr 6664fd6a1d7SAndreas Gohr /** 6674fd6a1d7SAndreas Gohr * Get a list of extensions that are similar to this one 6684fd6a1d7SAndreas Gohr * 6694fd6a1d7SAndreas Gohr * @return string[] 6704fd6a1d7SAndreas Gohr */ 6714fd6a1d7SAndreas Gohr public function getSimilarList() 6724fd6a1d7SAndreas Gohr { 6734fd6a1d7SAndreas Gohr return $this->getRemoteTag('similar', []); 6744fd6a1d7SAndreas Gohr } 6754fd6a1d7SAndreas Gohr 6764fd6a1d7SAndreas Gohr /** 6774fd6a1d7SAndreas Gohr * Get a list of extensions that are marked as conflicting with this one 6784fd6a1d7SAndreas Gohr * 6794fd6a1d7SAndreas Gohr * @return string[] 6804fd6a1d7SAndreas Gohr */ 6814fd6a1d7SAndreas Gohr public function getConflictList() 6824fd6a1d7SAndreas Gohr { 6834fd6a1d7SAndreas Gohr return $this->getRemoteTag('conflicts', []); 6844fd6a1d7SAndreas Gohr } 6854fd6a1d7SAndreas Gohr 6864fd6a1d7SAndreas Gohr /** 6874fd6a1d7SAndreas Gohr * Get a list of DokuWiki versions this plugin is marked as compatible with 6884fd6a1d7SAndreas Gohr * 6894fd6a1d7SAndreas Gohr * @return string[][] date -> version 6904fd6a1d7SAndreas Gohr */ 6914fd6a1d7SAndreas Gohr public function getCompatibleVersions() 6924fd6a1d7SAndreas Gohr { 6934fd6a1d7SAndreas Gohr return $this->getRemoteTag('compatible', []); 6944fd6a1d7SAndreas Gohr } 6954fd6a1d7SAndreas Gohr 6967c9966a5SAndreas Gohr // endregion 6977c9966a5SAndreas Gohr 698cf2dcf1bSAndreas Gohr // region Actions 699cf2dcf1bSAndreas Gohr 700cf2dcf1bSAndreas Gohr /** 701cf2dcf1bSAndreas Gohr * Install or update the extension 702cf2dcf1bSAndreas Gohr * 703cf2dcf1bSAndreas Gohr * @throws Exception 704cf2dcf1bSAndreas Gohr */ 705cf2dcf1bSAndreas Gohr public function installOrUpdate() 706cf2dcf1bSAndreas Gohr { 707cf2dcf1bSAndreas Gohr $installer = new Installer(true); 708160d3688SAndreas Gohr $installer->installExtension($this); 709cf2dcf1bSAndreas Gohr } 710cf2dcf1bSAndreas Gohr 711cf2dcf1bSAndreas Gohr /** 712cf2dcf1bSAndreas Gohr * Uninstall the extension 713cf2dcf1bSAndreas Gohr * @throws Exception 714cf2dcf1bSAndreas Gohr */ 715cf2dcf1bSAndreas Gohr public function uninstall() 716cf2dcf1bSAndreas Gohr { 717cf2dcf1bSAndreas Gohr $installer = new Installer(true); 718cf2dcf1bSAndreas Gohr $installer->uninstall($this); 719cf2dcf1bSAndreas Gohr } 720cf2dcf1bSAndreas Gohr 721cf2dcf1bSAndreas Gohr /** 7225732c960SAndreas Gohr * Toggle the extension between enabled and disabled 7235732c960SAndreas Gohr * @return void 7245732c960SAndreas Gohr * @throws Exception 7255732c960SAndreas Gohr */ 7265732c960SAndreas Gohr public function toggle() 7275732c960SAndreas Gohr { 7285732c960SAndreas Gohr if ($this->isEnabled()) { 7295732c960SAndreas Gohr $this->disable(); 7305732c960SAndreas Gohr } else { 7315732c960SAndreas Gohr $this->enable(); 7325732c960SAndreas Gohr } 7335732c960SAndreas Gohr } 7345732c960SAndreas Gohr 7355732c960SAndreas Gohr /** 736cf2dcf1bSAndreas Gohr * Enable the extension 737b69d74f1SAndreas Gohr * 738cf2dcf1bSAndreas Gohr * @throws Exception 739cf2dcf1bSAndreas Gohr */ 740cf2dcf1bSAndreas Gohr public function enable() 741cf2dcf1bSAndreas Gohr { 742b69d74f1SAndreas Gohr (new Installer())->enable($this); 743cf2dcf1bSAndreas Gohr } 744cf2dcf1bSAndreas Gohr 745cf2dcf1bSAndreas Gohr /** 746cf2dcf1bSAndreas Gohr * Disable the extension 747b69d74f1SAndreas Gohr * 748cf2dcf1bSAndreas Gohr * @throws Exception 749cf2dcf1bSAndreas Gohr */ 750cf2dcf1bSAndreas Gohr public function disable() 751cf2dcf1bSAndreas Gohr { 752b69d74f1SAndreas Gohr (new Installer())->disable($this); 753cf2dcf1bSAndreas Gohr } 754cf2dcf1bSAndreas Gohr 755cf2dcf1bSAndreas Gohr // endregion 756cf2dcf1bSAndreas Gohr 757cf2dcf1bSAndreas Gohr // region Meta Data Management 758cf2dcf1bSAndreas Gohr 759cf2dcf1bSAndreas Gohr /** 7607c9966a5SAndreas Gohr * Access the Manager for this extension 761cf2dcf1bSAndreas Gohr * 7627c9966a5SAndreas Gohr * @return Manager 763cf2dcf1bSAndreas Gohr */ 7647c9966a5SAndreas Gohr public function getManager() 765cf2dcf1bSAndreas Gohr { 7667c184cfcSAndreas Gohr if (!$this->manager instanceof Manager) { 7677c9966a5SAndreas Gohr $this->manager = new Manager($this); 768cf2dcf1bSAndreas Gohr } 7697c9966a5SAndreas Gohr return $this->manager; 770cf2dcf1bSAndreas Gohr } 771cf2dcf1bSAndreas Gohr 772cf2dcf1bSAndreas Gohr /** 773cf2dcf1bSAndreas Gohr * Reads the info file of the extension if available and fills the localInfo array 774cf2dcf1bSAndreas Gohr */ 775cf2dcf1bSAndreas Gohr protected function readLocalInfo() 776cf2dcf1bSAndreas Gohr { 777a1e045f7SAndreas Gohr if (!$this->getCurrentDir()) return; 778cf2dcf1bSAndreas Gohr $file = $this->currentDir . '/' . $this->type . '.info.txt'; 779cf2dcf1bSAndreas Gohr if (!is_readable($file)) return; 780cf2dcf1bSAndreas Gohr $this->localInfo = confToHash($file, true); 781cf2dcf1bSAndreas Gohr $this->localInfo = array_filter($this->localInfo); // remove all falsy keys 782cf2dcf1bSAndreas Gohr } 783cf2dcf1bSAndreas Gohr 784cf2dcf1bSAndreas Gohr /** 785*a1ef4d62SAndreas Gohr * Try to determine the extension's base name from a plugin class name 786*a1ef4d62SAndreas Gohr * 787*a1ef4d62SAndreas Gohr * We use this as a fallback for old plugins without an info file 788*a1ef4d62SAndreas Gohr * 789*a1ef4d62SAndreas Gohr * @param string $dir The directory where the extension is located 790*a1ef4d62SAndreas Gohr * @return string 791*a1ef4d62SAndreas Gohr */ 792*a1ef4d62SAndreas Gohr protected function getBaseFromClass($dir) 793*a1ef4d62SAndreas Gohr { 794*a1ef4d62SAndreas Gohr foreach (Extension::COMPONENT_FILES as $type) { 795*a1ef4d62SAndreas Gohr $file = $dir . '/' . $type . '.php'; 796*a1ef4d62SAndreas Gohr if (!is_readable($file)) continue; 797*a1ef4d62SAndreas Gohr $class = $this->getClassNameFromFile($file); 798*a1ef4d62SAndreas Gohr if ($class === null) continue; 799*a1ef4d62SAndreas Gohr if (preg_match('/' . $type . '_plugin_(\w+)/', $class, $matches)) { 800*a1ef4d62SAndreas Gohr return $matches[1]; 801*a1ef4d62SAndreas Gohr } 802*a1ef4d62SAndreas Gohr } 803*a1ef4d62SAndreas Gohr return ''; 804*a1ef4d62SAndreas Gohr } 805*a1ef4d62SAndreas Gohr 806*a1ef4d62SAndreas Gohr /** 807cf2dcf1bSAndreas Gohr * Fetches the remote info from the repository 808cf2dcf1bSAndreas Gohr * 809cf2dcf1bSAndreas Gohr * This ignores any errors coming from the repository and just sets the remoteInfo to an empty array in that case 810cf2dcf1bSAndreas Gohr */ 811cf2dcf1bSAndreas Gohr protected function loadRemoteInfo() 812cf2dcf1bSAndreas Gohr { 813cf2dcf1bSAndreas Gohr if ($this->remoteInfo) return; 814cf2dcf1bSAndreas Gohr $remote = Repository::getInstance(); 815cf2dcf1bSAndreas Gohr try { 816cf2dcf1bSAndreas Gohr $this->remoteInfo = (array)$remote->getExtensionData($this->getId()); 817cf2dcf1bSAndreas Gohr } catch (Exception $e) { 818cf2dcf1bSAndreas Gohr $this->remoteInfo = []; 819cf2dcf1bSAndreas Gohr } 820cf2dcf1bSAndreas Gohr } 821cf2dcf1bSAndreas Gohr 822cf2dcf1bSAndreas Gohr /** 823cf2dcf1bSAndreas Gohr * Read information from either local or remote info 824cf2dcf1bSAndreas Gohr * 8257c9966a5SAndreas Gohr * Always prefers local info over remote info. Giving multiple keys is useful when the 8267c9966a5SAndreas Gohr * key has been renamed in the past or if local and remote keys might differ. 827cf2dcf1bSAndreas Gohr * 828cf2dcf1bSAndreas Gohr * @param string|string[] $tag one or multiple keys to check 829cf2dcf1bSAndreas Gohr * @param mixed $default 830cf2dcf1bSAndreas Gohr * @return mixed 831cf2dcf1bSAndreas Gohr */ 832cf2dcf1bSAndreas Gohr protected function getTag($tag, $default = '') 833cf2dcf1bSAndreas Gohr { 834cf2dcf1bSAndreas Gohr foreach ((array)$tag as $t) { 835cf2dcf1bSAndreas Gohr if (isset($this->localInfo[$t])) return $this->localInfo[$t]; 836cf2dcf1bSAndreas Gohr } 8377c9966a5SAndreas Gohr 8387c9966a5SAndreas Gohr return $this->getRemoteTag($tag, $default); 8397c9966a5SAndreas Gohr } 8407c9966a5SAndreas Gohr 8417c9966a5SAndreas Gohr /** 8427c9966a5SAndreas Gohr * Read information from remote info 8437c9966a5SAndreas Gohr * 8447c9966a5SAndreas Gohr * @param string|string[] $tag one or mutiple keys to check 8457c9966a5SAndreas Gohr * @param mixed $default 8467c9966a5SAndreas Gohr * @return mixed 8477c9966a5SAndreas Gohr */ 8487c9966a5SAndreas Gohr protected function getRemoteTag($tag, $default = '') 8497c9966a5SAndreas Gohr { 850cf2dcf1bSAndreas Gohr $this->loadRemoteInfo(); 851cf2dcf1bSAndreas Gohr foreach ((array)$tag as $t) { 852cf2dcf1bSAndreas Gohr if (isset($this->remoteInfo[$t])) return $this->remoteInfo[$t]; 853cf2dcf1bSAndreas Gohr } 854cf2dcf1bSAndreas Gohr return $default; 855cf2dcf1bSAndreas Gohr } 856cf2dcf1bSAndreas Gohr 857cf2dcf1bSAndreas Gohr // endregion 858a1e045f7SAndreas Gohr 859a1e045f7SAndreas Gohr // region utilities 860a1e045f7SAndreas Gohr 861a1e045f7SAndreas Gohr /** 862a1e045f7SAndreas Gohr * Convert an extension id to a type and base 863a1e045f7SAndreas Gohr * 864a1e045f7SAndreas Gohr * @param string $id 865a1e045f7SAndreas Gohr * @return array [type, base] 866a1e045f7SAndreas Gohr */ 867a1e045f7SAndreas Gohr protected function idToTypeBase($id) 868a1e045f7SAndreas Gohr { 869a1e045f7SAndreas Gohr [$type, $base] = sexplode(':', $id, 2); 870a1e045f7SAndreas Gohr if ($base === null) { 871a1e045f7SAndreas Gohr $base = $type; 872a1e045f7SAndreas Gohr $type = self::TYPE_PLUGIN; 873a1e045f7SAndreas Gohr } elseif ($type === self::TYPE_TEMPLATE) { 874a1e045f7SAndreas Gohr $type = self::TYPE_TEMPLATE; 875a1e045f7SAndreas Gohr } else { 876a1e045f7SAndreas Gohr throw new RuntimeException('Invalid extension id: ' . $id); 877a1e045f7SAndreas Gohr } 878a1e045f7SAndreas Gohr 879a1e045f7SAndreas Gohr return [$type, $base]; 880a1e045f7SAndreas Gohr } 881b69d74f1SAndreas Gohr 8827c9966a5SAndreas Gohr /** 883*a1ef4d62SAndreas Gohr * Extract the class name from a file 884*a1ef4d62SAndreas Gohr * 885*a1ef4d62SAndreas Gohr * @param string $filePath 886*a1ef4d62SAndreas Gohr * @return string|null 887*a1ef4d62SAndreas Gohr */ 888*a1ef4d62SAndreas Gohr protected function getClassNameFromFile($filePath) 889*a1ef4d62SAndreas Gohr { 890*a1ef4d62SAndreas Gohr $code = file_get_contents($filePath); 891*a1ef4d62SAndreas Gohr $tokens = token_get_all($code); 892*a1ef4d62SAndreas Gohr 893*a1ef4d62SAndreas Gohr for ($i = 0, $count = count($tokens); $i < $count; $i++) { 894*a1ef4d62SAndreas Gohr if (is_array($tokens[$i]) && $tokens[$i][0] === T_CLASS) { 895*a1ef4d62SAndreas Gohr // Skip whitespace/comments after T_CLASS 896*a1ef4d62SAndreas Gohr $j = $i + 1; 897*a1ef4d62SAndreas Gohr while ( 898*a1ef4d62SAndreas Gohr isset($tokens[$j]) && 899*a1ef4d62SAndreas Gohr is_array($tokens[$j]) && 900*a1ef4d62SAndreas Gohr in_array($tokens[$j][0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT]) 901*a1ef4d62SAndreas Gohr ) { 902*a1ef4d62SAndreas Gohr $j++; 903*a1ef4d62SAndreas Gohr } 904*a1ef4d62SAndreas Gohr 905*a1ef4d62SAndreas Gohr // The next token should be the class name 906*a1ef4d62SAndreas Gohr if (isset($tokens[$j]) && is_array($tokens[$j]) && $tokens[$j][0] === T_STRING) { 907*a1ef4d62SAndreas Gohr return $tokens[$j][1]; // Return class name 908*a1ef4d62SAndreas Gohr } 909*a1ef4d62SAndreas Gohr } 910*a1ef4d62SAndreas Gohr } 911*a1ef4d62SAndreas Gohr 912*a1ef4d62SAndreas Gohr return null; // No class found 913*a1ef4d62SAndreas Gohr } 914*a1ef4d62SAndreas Gohr 915*a1ef4d62SAndreas Gohr /** 9167c9966a5SAndreas Gohr * @return string 9177c9966a5SAndreas Gohr */ 9187c9966a5SAndreas Gohr public function __toString() 9197c9966a5SAndreas Gohr { 9207c9966a5SAndreas Gohr return $this->getId(); 9217c9966a5SAndreas Gohr } 9227c9966a5SAndreas Gohr 923a1e045f7SAndreas Gohr // endregion 924cf2dcf1bSAndreas Gohr} 925