3 * DokuWiki Plugin extension (Helper Component)
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author Michael Hamann <michael@content-space.de>
9 use dokuwiki\HTTP\DokuHTTPClient
;
10 use dokuwiki\Extension\PluginController
;
13 * Class helper_plugin_extension_extension represents a single extension (plugin or template)
15 class helper_plugin_extension_extension
extends DokuWiki_Plugin
19 private $is_template = false;
23 /** @var helper_plugin_extension_repository $repository */
24 private $repository = null;
26 /** @var array list of temporary directories */
27 private $temporary = array();
29 /** @var string where templates are installed to */
33 * helper_plugin_extension_extension constructor.
35 public function __construct()
37 $this->tpllib
= dirname(tpl_incdir()).'/';
43 * deletes any dangling temporary directories
45 public function __destruct()
47 foreach ($this->temporary
as $dir) {
53 * @return bool false, this component is not a singleton
55 public function isSingleton()
61 * Set the name of the extension this instance shall represents, triggers loading the local and remote data
63 * @param string $id The id of the extension (prefixed with template: for templates)
64 * @return bool If some (local or remote) data was found
66 public function setExtension($id)
72 if (substr($id, 0, 9) == 'template:') {
73 $this->base
= substr($id, 9);
74 $this->is_template
= true;
76 $this->is_template
= false;
79 $this->localInfo
= array();
80 $this->managerData
= array();
81 $this->remoteInfo
= array();
83 if ($this->isInstalled()) {
84 $this->readLocalData();
85 $this->readManagerData();
88 if ($this->repository
== null) {
89 $this->repository
= $this->loadHelper('extension_repository');
92 $this->remoteInfo
= $this->repository
->getData($this->getID());
94 return ($this->localInfo ||
$this->remoteInfo
);
98 * If the extension is installed locally
100 * @return bool If the extension is installed locally
102 public function isInstalled()
104 return is_dir($this->getInstallDir());
108 * If the extension is under git control
112 public function isGitControlled()
114 if (!$this->isInstalled()) return false;
115 return is_dir($this->getInstallDir().'/.git');
119 * If the extension is bundled
121 * @return bool If the extension is bundled
123 public function isBundled()
125 if (!empty($this->remoteInfo
['bundled'])) return $this->remoteInfo
['bundled'];
129 'authad', 'authldap', 'authmysql', 'authpdo',
130 'authpgsql', 'authplain', 'acl', 'info', 'extension',
131 'revert', 'popularity', 'config', 'safefnrecode', 'styling',
132 'testing', 'template:dokuwiki'
138 * If the extension is protected against any modification (disable/uninstall)
140 * @return bool if the extension is protected
142 public function isProtected()
144 // never allow deinstalling the current auth plugin:
146 if ($this->id
== $conf['authtype']) return true;
148 /** @var PluginController $plugin_controller */
149 global $plugin_controller;
150 $cascade = $plugin_controller->getCascade();
151 return (isset($cascade['protected'][$this->id
]) && $cascade['protected'][$this->id
]);
155 * If the extension is installed in the correct directory
157 * @return bool If the extension is installed in the correct directory
159 public function isInWrongFolder()
161 return $this->base
!= $this->getBase();
165 * If the extension is enabled
167 * @return bool If the extension is enabled
169 public function isEnabled()
172 if ($this->isTemplate()) {
173 return ($conf['template'] == $this->getBase());
176 /* @var PluginController $plugin_controller */
177 global $plugin_controller;
178 return !$plugin_controller->isdisabled($this->base
);
182 * If the extension should be updated, i.e. if an updated version is available
184 * @return bool If an update is available
186 public function updateAvailable()
188 if (!$this->isInstalled()) return false;
189 if ($this->isBundled()) return false;
190 $lastupdate = $this->getLastUpdate();
191 if ($lastupdate === false) return false;
192 $installed = $this->getInstalledVersion();
193 if ($installed === false ||
$installed === $this->getLang('unknownversion')) return true;
194 return $this->getInstalledVersion() < $this->getLastUpdate();
198 * If the extension is a template
200 * @return bool If this extension is a template
202 public function isTemplate()
204 return $this->is_template
;
208 * Get the ID of the extension
210 * This is the same as getName() for plugins, for templates it's getName() prefixed with 'template:'
214 public function getID()
220 * Get the name of the installation directory
222 * @return string The name of the installation directory
224 public function getInstallName()
229 // Data from plugin.info.txt/template.info.txt or the repo when not available locally
231 * Get the basename of the extension
233 * @return string The basename
235 public function getBase()
237 if (!empty($this->localInfo
['base'])) return $this->localInfo
['base'];
242 * Get the display name of the extension
244 * @return string The display name
246 public function getDisplayName()
248 if (!empty($this->localInfo
['name'])) return $this->localInfo
['name'];
249 if (!empty($this->remoteInfo
['name'])) return $this->remoteInfo
['name'];
254 * Get the author name of the extension
256 * @return string|bool The name of the author or false if there is none
258 public function getAuthor()
260 if (!empty($this->localInfo
['author'])) return $this->localInfo
['author'];
261 if (!empty($this->remoteInfo
['author'])) return $this->remoteInfo
['author'];
266 * Get the email of the author of the extension if there is any
268 * @return string|bool The email address or false if there is none
270 public function getEmail()
272 // email is only in the local data
273 if (!empty($this->localInfo
['email'])) return $this->localInfo
['email'];
278 * Get the email id, i.e. the md5sum of the email
280 * @return string|bool The md5sum of the email if there is any, false otherwise
282 public function getEmailID()
284 if (!empty($this->remoteInfo
['emailid'])) return $this->remoteInfo
['emailid'];
285 if (!empty($this->localInfo
['email'])) return md5($this->localInfo
['email']);
290 * Get the description of the extension
292 * @return string The description
294 public function getDescription()
296 if (!empty($this->localInfo
['desc'])) return $this->localInfo
['desc'];
297 if (!empty($this->remoteInfo
['description'])) return $this->remoteInfo
['description'];
302 * Get the URL of the extension, usually a page on dokuwiki.org
304 * @return string The URL
306 public function getURL()
308 if (!empty($this->localInfo
['url'])) return $this->localInfo
['url'];
309 return 'https://www.dokuwiki.org/'.($this->isTemplate() ?
'template' : 'plugin').':'.$this->getBase();
313 * Get the installed version of the extension
315 * @return string|bool The version, usually in the form yyyy-mm-dd if there is any
317 public function getInstalledVersion()
319 if (!empty($this->localInfo
['date'])) return $this->localInfo
['date'];
320 if ($this->isInstalled()) return $this->getLang('unknownversion');
325 * Get the install date of the current version
327 * @return string|bool The date of the last update or false if not available
329 public function getUpdateDate()
331 if (!empty($this->managerData
['updated'])) return $this->managerData
['updated'];
332 return $this->getInstallDate();
336 * Get the date of the installation of the plugin
338 * @return string|bool The date of the installation or false if not available
340 public function getInstallDate()
342 if (!empty($this->managerData
['installed'])) return $this->managerData
['installed'];
347 * Get the names of the dependencies of this extension
349 * @return array The base names of the dependencies
351 public function getDependencies()
353 if (!empty($this->remoteInfo
['dependencies'])) return $this->remoteInfo
['dependencies'];
358 * Get the names of the missing dependencies
360 * @return array The base names of the missing dependencies
362 public function getMissingDependencies()
364 /* @var PluginController $plugin_controller */
365 global $plugin_controller;
366 $dependencies = $this->getDependencies();
367 $missing_dependencies = array();
368 foreach ($dependencies as $dependency) {
369 if ($plugin_controller->isdisabled($dependency)) {
370 $missing_dependencies[] = $dependency;
373 return $missing_dependencies;
377 * Get the names of all conflicting extensions
379 * @return array The names of the conflicting extensions
381 public function getConflicts()
383 if (!empty($this->remoteInfo
['conflicts'])) return $this->remoteInfo
['conflicts'];
388 * Get the names of similar extensions
390 * @return array The names of similar extensions
392 public function getSimilarExtensions()
394 if (!empty($this->remoteInfo
['similar'])) return $this->remoteInfo
['similar'];
399 * Get the names of the tags of the extension
401 * @return array The names of the tags of the extension
403 public function getTags()
405 if (!empty($this->remoteInfo
['tags'])) return $this->remoteInfo
['tags'];
410 * Get the popularity information as floating point number [0,1]
412 * @return float|bool The popularity information or false if it isn't available
414 public function getPopularity()
416 if (!empty($this->remoteInfo
['popularity'])) return $this->remoteInfo
['popularity'];
422 * Get the text of the security warning if there is any
424 * @return string|bool The security warning if there is any, false otherwise
426 public function getSecurityWarning()
428 if (!empty($this->remoteInfo
['securitywarning'])) return $this->remoteInfo
['securitywarning'];
433 * Get the text of the security issue if there is any
435 * @return string|bool The security issue if there is any, false otherwise
437 public function getSecurityIssue()
439 if (!empty($this->remoteInfo
['securityissue'])) return $this->remoteInfo
['securityissue'];
444 * Get the URL of the screenshot of the extension if there is any
446 * @return string|bool The screenshot URL if there is any, false otherwise
448 public function getScreenshotURL()
450 if (!empty($this->remoteInfo
['screenshoturl'])) return $this->remoteInfo
['screenshoturl'];
455 * Get the URL of the thumbnail of the extension if there is any
457 * @return string|bool The thumbnail URL if there is any, false otherwise
459 public function getThumbnailURL()
461 if (!empty($this->remoteInfo
['thumbnailurl'])) return $this->remoteInfo
['thumbnailurl'];
465 * Get the last used download URL of the extension if there is any
467 * @return string|bool The previously used download URL, false if the extension has been installed manually
469 public function getLastDownloadURL()
471 if (!empty($this->managerData
['downloadurl'])) return $this->managerData
['downloadurl'];
476 * Get the download URL of the extension if there is any
478 * @return string|bool The download URL if there is any, false otherwise
480 public function getDownloadURL()
482 if (!empty($this->remoteInfo
['downloadurl'])) return $this->remoteInfo
['downloadurl'];
487 * If the download URL has changed since the last download
489 * @return bool If the download URL has changed
491 public function hasDownloadURLChanged()
493 $lasturl = $this->getLastDownloadURL();
494 $currenturl = $this->getDownloadURL();
495 return ($lasturl && $currenturl && $lasturl != $currenturl);
499 * Get the bug tracker URL of the extension if there is any
501 * @return string|bool The bug tracker URL if there is any, false otherwise
503 public function getBugtrackerURL()
505 if (!empty($this->remoteInfo
['bugtracker'])) return $this->remoteInfo
['bugtracker'];
510 * Get the URL of the source repository if there is any
512 * @return string|bool The URL of the source repository if there is any, false otherwise
514 public function getSourcerepoURL()
516 if (!empty($this->remoteInfo
['sourcerepo'])) return $this->remoteInfo
['sourcerepo'];
521 * Get the donation URL of the extension if there is any
523 * @return string|bool The donation URL if there is any, false otherwise
525 public function getDonationURL()
527 if (!empty($this->remoteInfo
['donationurl'])) return $this->remoteInfo
['donationurl'];
532 * Get the extension type(s)
534 * @return array The type(s) as array of strings
536 public function getTypes()
538 if (!empty($this->remoteInfo
['types'])) return $this->remoteInfo
['types'];
539 if ($this->isTemplate()) return array(32 => 'template');
544 * Get a list of all DokuWiki versions this extension is compatible with
546 * @return array The versions in the form yyyy-mm-dd => ('label' => label, 'implicit' => implicit)
548 public function getCompatibleVersions()
550 if (!empty($this->remoteInfo
['compatible'])) return $this->remoteInfo
['compatible'];
555 * Get the date of the last available update
557 * @return string|bool The last available update in the form yyyy-mm-dd if there is any, false otherwise
559 public function getLastUpdate()
561 if (!empty($this->remoteInfo
['lastupdate'])) return $this->remoteInfo
['lastupdate'];
566 * Get the base path of the extension
568 * @return string The base path of the extension
570 public function getInstallDir()
572 if ($this->isTemplate()) {
573 return $this->tpllib
.$this->base
;
575 return DOKU_PLUGIN
.$this->base
;
580 * The type of extension installation
582 * @return string One of "none", "manual", "git" or "automatic"
584 public function getInstallType()
586 if (!$this->isInstalled()) return 'none';
587 if (!empty($this->managerData
)) return 'automatic';
588 if (is_dir($this->getInstallDir().'/.git')) return 'git';
593 * If the extension can probably be installed/updated or uninstalled
595 * @return bool|string True or error string
597 public function canModify()
599 if ($this->isInstalled()) {
600 if (!is_writable($this->getInstallDir())) {
605 if ($this->isTemplate() && !is_writable($this->tpllib
)) {
607 } elseif (!is_writable(DOKU_PLUGIN
)) {
608 return 'nopluginperms';
614 * Install an extension from a user upload
616 * @param string $field name of the upload file
617 * @throws Exception when something goes wrong
618 * @return array The list of installed extensions
620 public function installFromUpload($field)
622 if ($_FILES[$field]['error']) {
623 throw new Exception($this->getLang('msg_upload_failed').' ('.$_FILES[$field]['error'].')');
626 $tmp = $this->mkTmpDir();
627 if (!$tmp) throw new Exception($this->getLang('error_dircreate'));
629 // filename may contain the plugin name for old style plugins...
630 $basename = basename($_FILES[$field]['name']);
631 $basename = preg_replace('/\.(tar\.gz|tar\.bz|tar\.bz2|tar|tgz|tbz|zip)$/', '', $basename);
632 $basename = preg_replace('/[\W]+/', '', $basename);
634 if (!move_uploaded_file($_FILES[$field]['tmp_name'], "$tmp/upload.archive")) {
635 throw new Exception($this->getLang('msg_upload_failed'));
639 $installed = $this->installArchive("$tmp/upload.archive", true, $basename);
640 $this->updateManagerData('', $installed);
641 $this->removeDeletedfiles($installed);
644 } catch (Exception
$e) {
651 * Install an extension from a remote URL
654 * @throws Exception when something goes wrong
655 * @return array The list of installed extensions
657 public function installFromURL($url)
660 $path = $this->download($url);
661 $installed = $this->installArchive($path, true);
662 $this->updateManagerData($url, $installed);
663 $this->removeDeletedfiles($installed);
667 } catch (Exception
$e) {
674 * Install or update the extension
676 * @throws \Exception when something goes wrong
677 * @return array The list of installed extensions
679 public function installOrUpdate()
681 $url = $this->getDownloadURL();
682 $path = $this->download($url);
683 $installed = $this->installArchive($path, $this->isInstalled(), $this->getBase());
684 $this->updateManagerData($url, $installed);
686 // refresh extension information
687 if (!isset($installed[$this->getID()])) {
688 throw new Exception('Error, the requested extension hasn\'t been installed or updated');
690 $this->removeDeletedfiles($installed);
691 $this->setExtension($this->getID());
697 * Uninstall the extension
699 * @return bool If the plugin was sucessfully uninstalled
701 public function uninstall()
704 return io_rmdir($this->getInstallDir(), true);
708 * Enable the extension
710 * @return bool|string True or an error message
712 public function enable()
714 if ($this->isTemplate()) return $this->getLang('notimplemented');
715 if (!$this->isInstalled()) return $this->getLang('notinstalled');
716 if ($this->isEnabled()) return $this->getLang('alreadyenabled');
718 /* @var PluginController $plugin_controller */
719 global $plugin_controller;
720 if ($plugin_controller->enable($this->base
)) {
724 return $this->getLang('pluginlistsaveerror');
729 * Disable the extension
731 * @return bool|string True or an error message
733 public function disable()
735 if ($this->isTemplate()) return $this->getLang('notimplemented');
737 /* @var PluginController $plugin_controller */
738 global $plugin_controller;
739 if (!$this->isInstalled()) return $this->getLang('notinstalled');
740 if (!$this->isEnabled()) return $this->getLang('alreadydisabled');
741 if ($plugin_controller->disable($this->base
)) {
745 return $this->getLang('pluginlistsaveerror');
750 * Purge the cache by touching the main configuration file
752 protected function purgeCache()
754 global $config_cascade;
756 // expire dokuwiki caches
757 // touching local.php expires wiki page, JS and CSS caches
758 @touch
(reset($config_cascade['main']['local']));
762 * Read local extension data either from info.txt or getInfo()
764 protected function readLocalData()
766 if ($this->isTemplate()) {
767 $infopath = $this->getInstallDir().'/template.info.txt';
769 $infopath = $this->getInstallDir().'/plugin.info.txt';
772 if (is_readable($infopath)) {
773 $this->localInfo
= confToHash($infopath);
774 } elseif (!$this->isTemplate() && $this->isEnabled()) {
775 $path = $this->getInstallDir().'/';
778 foreach (PluginController
::PLUGIN_TYPES
as $type) {
779 if (file_exists($path.$type.'.php')) {
780 $plugin = plugin_load($type, $this->base
);
784 if ($dh = @opendir
($path.$type.'/')) {
785 while (false !== ($cp = readdir($dh))) {
786 if ($cp == '.' ||
$cp == '..' ||
strtolower(substr($cp, -4)) != '.php') continue;
788 $plugin = plugin_load($type, $this->base
.'_'.substr($cp, 0, -4));
797 /* @var DokuWiki_Plugin $plugin */
798 $this->localInfo
= $plugin->getInfo();
804 * Save the given URL and current datetime in the manager.dat file of all installed extensions
806 * @param string $url Where the extension was downloaded from. (empty for manual installs via upload)
807 * @param array $installed Optional list of installed plugins
809 protected function updateManagerData($url = '', $installed = null)
811 $origID = $this->getID();
813 if (is_null($installed)) {
814 $installed = array($origID);
817 foreach ($installed as $ext => $info) {
818 if ($this->getID() != $ext) $this->setExtension($ext);
820 $this->managerData
['downloadurl'] = $url;
821 } elseif (isset($this->managerData
['downloadurl'])) {
822 unset($this->managerData
['downloadurl']);
824 if (isset($this->managerData
['installed'])) {
825 $this->managerData
['updated'] = date('r');
827 $this->managerData
['installed'] = date('r');
829 $this->writeManagerData();
832 if ($this->getID() != $origID) $this->setExtension($origID);
836 * Read the manager.dat file
838 protected function readManagerData()
840 $managerpath = $this->getInstallDir().'/manager.dat';
841 if (is_readable($managerpath)) {
842 $file = @file
($managerpath);
844 foreach ($file as $line) {
845 list($key, $value) = explode('=', trim($line, DOKU_LF
), 2);
847 $value = trim($value);
848 // backwards compatible with old plugin manager
849 if ($key == 'url') $key = 'downloadurl';
850 $this->managerData
[$key] = $value;
857 * Write the manager.data file
859 protected function writeManagerData()
861 $managerpath = $this->getInstallDir().'/manager.dat';
863 foreach ($this->managerData
as $k => $v) {
864 $data .= $k.'='.$v.DOKU_LF
;
866 io_saveFile($managerpath, $data);
870 * Returns a temporary directory
872 * The directory is registered for cleanup when the class is destroyed
874 * @return false|string
876 protected function mkTmpDir()
878 $dir = io_mktmpdir();
879 if (!$dir) return false;
880 $this->temporary
[] = $dir;
885 * downloads a file from the net and saves it
887 * - $file is the directory where the file should be saved
888 * - if successful will return the name used for the saved file, false otherwise
890 * @author Andreas Gohr <andi@splitbrain.org>
891 * @author Chris Smith <chris@jalakai.co.uk>
893 * @param string $url url to download
894 * @param string $file path to file or directory where to save
895 * @param string $defaultName fallback for name of download
896 * @return bool|string if failed false, otherwise true or the name of the file in the given dir
898 protected function downloadToFile($url,$file,$defaultName=''){
900 $http = new DokuHTTPClient();
901 $http->max_bodysize
= 0;
902 $http->timeout
= 25; //max. 25 sec
903 $http->keep_alive
= false; // we do single ops here, no need for keep-alive
904 $http->agent
= 'DokuWiki HTTP Client (Extension Manager)';
906 $data = $http->get($url);
907 if ($data === false) return false;
910 if (isset($http->resp_headers
['content-disposition'])) {
911 $content_disposition = $http->resp_headers
['content-disposition'];
913 if (is_string($content_disposition) &&
914 preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) {
916 $name = \dokuwiki\Utf8\PhpString
::basename($match[1]);
922 if (!$defaultName) return false;
923 $name = $defaultName;
928 $fileexists = file_exists($file);
929 $fp = @fopen
($file,"w");
930 if(!$fp) return false;
933 if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
938 * Download an archive to a protected path
940 * @param string $url The url to get the archive from
941 * @throws Exception when something goes wrong
942 * @return string The path where the archive was saved
944 public function download($url)
947 if (!preg_match('/https?:\/\//i', $url)) {
948 throw new Exception($this->getLang('error_badurl'));
951 // try to get the file from the path (used as plugin name fallback)
952 $file = parse_url($url, PHP_URL_PATH
);
953 if (is_null($file)) {
956 $file = \dokuwiki\Utf8\PhpString
::basename($file);
959 // create tmp directory for download
960 if (!($tmp = $this->mkTmpDir())) {
961 throw new Exception($this->getLang('error_dircreate'));
965 if (!$file = $this->downloadToFile($url, $tmp.'/', $file)) {
966 io_rmdir($tmp, true);
967 throw new Exception(sprintf($this->getLang('error_download'), '<bdi>'.hsc($url).'</bdi>'));
970 return $tmp.'/'.$file;
974 * @param string $file The path to the archive that shall be installed
975 * @param bool $overwrite If an already installed plugin should be overwritten
976 * @param string $base The basename of the plugin if it's known
977 * @throws Exception when something went wrong
978 * @return array list of installed extensions
980 public function installArchive($file, $overwrite = false, $base = '')
982 $installed_extensions = array();
984 // create tmp directory for decompression
985 if (!($tmp = $this->mkTmpDir())) {
986 throw new Exception($this->getLang('error_dircreate'));
989 // add default base folder if specified to handle case where zip doesn't contain this
990 if ($base && !@mkdir
($tmp.'/'.$base)) {
991 throw new Exception($this->getLang('error_dircreate'));
995 $this->decompress($file, "$tmp/".$base);
997 // search $tmp/$base for the folder(s) that has been created
998 // move the folder(s) to lib/..
999 $result = array('old'=>array(), 'new'=>array());
1000 $default = ($this->isTemplate() ?
'template' : 'plugin');
1001 if (!$this->findFolders($result, $tmp.'/'.$base, $default)) {
1002 throw new Exception($this->getLang('error_findfolder'));
1005 // choose correct result array
1006 if (count($result['new'])) {
1007 $install = $result['new'];
1009 $install = $result['old'];
1012 if (!count($install)) {
1013 throw new Exception($this->getLang('error_findfolder'));
1016 // now install all found items
1017 foreach ($install as $item) {
1018 // where to install?
1019 if ($item['type'] == 'template') {
1020 $target_base_dir = $this->tpllib
;
1022 $target_base_dir = DOKU_PLUGIN
;
1025 if (!empty($item['base'])) {
1026 // use base set in info.txt
1027 } elseif ($base && count($install) == 1) {
1028 $item['base'] = $base;
1030 // default - use directory as found in zip
1031 // plugins from github/master without *.info.txt will install in wrong folder
1032 // but using $info->id will make 'code3' fail (which should install in lib/code/..)
1033 $item['base'] = basename($item['tmp']);
1036 // check to make sure we aren't overwriting anything
1037 $target = $target_base_dir.$item['base'];
1038 if (!$overwrite && file_exists($target)) {
1039 // TODO remember our settings, ask the user to confirm overwrite
1043 $action = file_exists($target) ?
'update' : 'install';
1046 if ($this->dircopy($item['tmp'], $target)) {
1048 $id = $item['base'];
1049 if ($item['type'] == 'template') {
1050 $id = 'template:'.$id;
1052 $installed_extensions[$id] = array(
1053 'base' => $item['base'],
1054 'type' => $item['type'],
1058 throw new Exception(sprintf($this->getLang('error_copy').DOKU_LF
, '<bdi>'.$item['base'].'</bdi>'));
1063 if ($tmp) io_rmdir($tmp, true);
1065 return $installed_extensions;
1069 * Find out what was in the extracted directory
1071 * Correct folders are searched recursively using the "*.info.txt" configs
1072 * as indicator for a root folder. When such a file is found, it's base
1073 * setting is used (when set). All folders found by this method are stored
1074 * in the 'new' key of the $result array.
1076 * For backwards compatibility all found top level folders are stored as
1077 * in the 'old' key of the $result array.
1079 * When no items are found in 'new' the copy mechanism should fall back
1082 * @author Andreas Gohr <andi@splitbrain.org>
1083 * @param array $result - results are stored here
1084 * @param string $directory - the temp directory where the package was unpacked to
1085 * @param string $default_type - type used if no info.txt available
1086 * @param string $subdir - a subdirectory. do not set. used by recursion
1087 * @return bool - false on error
1089 protected function findFolders(&$result, $directory, $default_type = 'plugin', $subdir = '')
1091 $this_dir = "$directory$subdir";
1092 $dh = @opendir
($this_dir);
1093 if (!$dh) return false;
1095 $found_dirs = array();
1097 $found_template_parts = 0;
1098 while (false !== ($f = readdir($dh))) {
1099 if ($f == '.' ||
$f == '..') continue;
1101 if (is_dir("$this_dir/$f")) {
1102 $found_dirs[] = "$subdir/$f";
1104 // it's a file -> check for config
1107 case 'plugin.info.txt':
1108 case 'template.info.txt':
1109 // we have found a clear marker, save and return
1111 $type = explode('.', $f, 2);
1112 $info['type'] = $type[0];
1113 $info['tmp'] = $this_dir;
1114 $conf = confToHash("$this_dir/$f");
1115 $info['base'] = basename($conf['base']);
1116 $result['new'][] = $info;
1121 case 'mediamanager.php':
1123 $found_template_parts++
;
1130 // files where found but no info.txt - use old method
1133 $info['tmp'] = $this_dir;
1134 // does this look like a template or should we use the default type?
1135 if ($found_template_parts >= 2) {
1136 $info['type'] = 'template';
1138 $info['type'] = $default_type;
1141 $result['old'][] = $info;
1145 // we have no files yet -> recurse
1146 foreach ($found_dirs as $found_dir) {
1147 $this->findFolders($result, $directory, $default_type, "$found_dir");
1153 * Decompress a given file to the given target directory
1155 * Determines the compression type from the file extension
1157 * @param string $file archive to extract
1158 * @param string $target directory to extract to
1162 private function decompress($file, $target)
1164 // decompression library doesn't like target folders ending in "/"
1165 if (substr($target, -1) == "/") $target = substr($target, 0, -1);
1167 $ext = $this->guessArchiveType($file);
1168 if (in_array($ext, array('tar', 'bz', 'gz'))) {
1170 $tar = new \splitbrain\PHPArchive\
Tar();
1172 $tar->extract($target);
1173 } catch (\splitbrain\PHPArchive\ArchiveIOException
$e) {
1174 throw new Exception($this->getLang('error_decompress').' '.$e->getMessage());
1178 } elseif ($ext == 'zip') {
1180 $zip = new \splitbrain\PHPArchive\
Zip();
1182 $zip->extract($target);
1183 } catch (\splitbrain\PHPArchive\ArchiveIOException
$e) {
1184 throw new Exception($this->getLang('error_decompress').' '.$e->getMessage());
1190 // the only case when we don't get one of the recognized archive types is when the archive file can't be read
1191 throw new Exception($this->getLang('error_decompress').' Couldn\'t read archive file');
1195 * Determine the archive type of the given file
1197 * Reads the first magic bytes of the given file for content type guessing,
1198 * if neither bz, gz or zip are recognized, tar is assumed.
1200 * @author Andreas Gohr <andi@splitbrain.org>
1201 * @param string $file The file to analyze
1202 * @return string|false false if the file can't be read, otherwise an "extension"
1204 private function guessArchiveType($file)
1206 $fh = fopen($file, 'rb');
1207 if (!$fh) return false;
1208 $magic = fread($fh, 5);
1211 if (strpos($magic, "\x42\x5a") === 0) return 'bz';
1212 if (strpos($magic, "\x1f\x8b") === 0) return 'gz';
1213 if (strpos($magic, "\x50\x4b\x03\x04") === 0) return 'zip';
1218 * Copy with recursive sub-directory support
1220 * @param string $src filename path to file
1221 * @param string $dst filename path to file
1222 * @return bool|int|string
1224 private function dircopy($src, $dst)
1229 if (!$dh = @opendir
($src)) return false;
1231 if ($ok = io_mkdir_p($dst)) {
1232 while ($ok && (false !== ($f = readdir($dh)))) {
1233 if ($f == '..' ||
$f == '.') continue;
1234 $ok = $this->dircopy("$src/$f", "$dst/$f");
1241 $exists = file_exists($dst);
1243 if (!@copy
($src, $dst)) return false;
1244 if (!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']);
1245 @touch
($dst, filemtime($src));
1252 * Delete outdated files from updated plugins
1254 * @param array $installed
1256 private function removeDeletedfiles($installed)
1258 foreach ($installed as $id => $extension) {
1260 if ($extension['action'] == 'install') continue;
1262 // get definition file
1263 if ($extension['type'] == 'template') {
1264 $extensiondir = $this->tpllib
;
1266 $extensiondir = DOKU_PLUGIN
;
1268 $extensiondir = $extensiondir . $extension['base'] .'/';
1269 $definitionfile = $extensiondir . 'deleted.files';
1270 if (!file_exists($definitionfile)) continue;
1272 // delete the old files
1273 $list = file($definitionfile);
1275 foreach ($list as $line) {
1276 $line = trim(preg_replace('/#.*$/', '', $line));
1277 if (!$line) continue;
1278 $file = $extensiondir . $line;
1279 if (!file_exists($file)) continue;
1281 io_rmdir($file, true);
1287 // vim:ts=4:sw=4:et: