3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * Provides tool_installaddon_installer class
21 * @package tool_installaddon
23 * @copyright 2013 David Mudrak <david@moodle.com>
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die();
30 * Implements main plugin features.
32 * @copyright 2013 David Mudrak <david@moodle.com>
33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 class tool_installaddon_installer {
37 /** @var tool_installaddon_installfromzip */
38 protected $installfromzipform = null;
41 * Factory method returning an instance of this class.
43 * @return tool_installaddon_installer
45 public static function instance() {
50 * Returns URL to the repository that addons can be searched in and installed from
54 public function get_addons_repository_url() {
57 if (!empty($CFG->config_php_settings['alternativeaddonsrepositoryurl'])) {
58 $url = $CFG->config_php_settings['alternativeaddonsrepositoryurl'];
60 $url = 'https://moodle.org/plugins/get.php';
63 if (!$this->should_send_site_info()) {
64 return new moodle_url($url);
67 // Append the basic information about our site.
69 'fullname' => $this->get_site_fullname(),
70 'url' => $this->get_site_url(),
71 'major_version' => $this->get_site_major_version(),
74 $site = $this->encode_site_information($site);
76 return new moodle_url($url, array('site' => $site));
80 * @return tool_installaddon_installfromzip
82 public function get_installfromzip_form() {
84 require_once(dirname(__FILE__).'/installfromzip_form.php');
86 if (!is_null($this->installfromzipform)) {
87 return $this->installfromzipform;
90 $action = new moodle_url('/admin/tool/installaddon/index.php');
91 $customdata = array('installer' => $this);
93 $this->installfromzipform = new tool_installaddon_installfromzip($action, $customdata);
95 return $this->installfromzipform;
99 * Saves the ZIP file from the {@link tool_installaddon_installfromzip} form
101 * The file is saved into the given temporary location for inspection and eventual
102 * deployment. The form is expected to be submitted and validated.
104 * @param tool_installaddon_installfromzip $form
105 * @param string $targetdir full path to the directory where the ZIP should be stored to
106 * @return string filename of the saved file relative to the given target
108 public function save_installfromzip_file(tool_installaddon_installfromzip $form, $targetdir) {
110 $filename = clean_param($form->get_new_filename('zipfile'), PARAM_FILE);
111 $form->save_file('zipfile', $targetdir.'/'.$filename);
117 * Extracts the saved file previously saved by {self::save_installfromzip_file()}
119 * The list of files found in the ZIP is returned via $zipcontentfiles parameter
120 * by reference. The format of that list is array of (string)filerelpath => (bool|string)
121 * where the array value is either true or a string describing the problematic file.
123 * @see zip_packer::extract_to_pathname()
124 * @param string $zipfilepath full path to the saved ZIP file
125 * @param string $targetdir full path to the directory to extract the ZIP file to
126 * @param string $rootdir explicitly rename the root directory of the ZIP into this non-empty value
127 * @param array list of extracted files as returned by {@link zip_packer::extract_to_pathname()}
129 public function extract_installfromzip_file($zipfilepath, $targetdir, $rootdir = '') {
131 require_once($CFG->libdir.'/filelib.php');
133 $fp = get_file_packer('application/zip');
134 $files = $fp->extract_to_pathname($zipfilepath, $targetdir);
137 if (!empty($rootdir)) {
138 $files = $this->rename_extracted_rootdir($targetdir, $rootdir, $files);
148 * Returns localised list of available plugin types
150 * @return array (string)plugintype => (string)plugin name
152 public function get_plugin_types_menu() {
154 require_once($CFG->libdir.'/pluginlib.php');
156 $pluginman = plugin_manager::instance();
158 $menu = array('' => get_string('choosedots'));
159 foreach (array_keys($pluginman->get_plugin_types()) as $plugintype) {
160 $menu[$plugintype] = $pluginman->plugintype_name($plugintype).' ('.$plugintype.')';
167 * Returns the full path of the root of the given plugin type
169 * Null is returned if the plugin type is not known. False is returned if the plugin type
170 * root is expected but not found. Otherwise, string is returned.
172 * @param string $plugintype
173 * @return string|bool|null
175 public function get_plugintype_root($plugintype) {
177 $plugintypepath = null;
178 foreach (get_plugin_types() as $type => $fullpath) {
179 if ($type === $plugintype) {
180 $plugintypepath = $fullpath;
184 if (is_null($plugintypepath)) {
188 if (!is_dir($plugintypepath)) {
192 return $plugintypepath;
196 * Is it possible to create a new plugin directory for the given plugin type?
198 * @throws coding_exception for invalid plugin types or non-existing plugin type locations
199 * @param string $plugintype
202 public function is_plugintype_writable($plugintype) {
204 $plugintypepath = $this->get_plugintype_root($plugintype);
206 if (is_null($plugintypepath)) {
207 throw new coding_exception('Unknown plugin type!');
210 if ($plugintypepath === false) {
211 throw new coding_exception('Plugin type location does not exist!');
214 return is_writable($plugintypepath);
217 //// End of external API ///////////////////////////////////////////////////
220 * @see self::instance()
222 protected function __construct() {
226 * @return string this site full name
228 protected function get_site_fullname() {
231 return $SITE->fullname;
235 * @return string this site URL
237 protected function get_site_url() {
240 return $CFG->wwwroot;
244 * @return string major version like 2.5, 2.6 etc.
246 protected function get_site_major_version() {
247 return moodle_major_version();
251 * Encodes the given array in a way that can be safely appended as HTTP GET param
253 * Be ware! The recipient may rely on the exact way how the site information is encoded.
254 * Do not change anything here unless you know what you are doing and understand all
255 * consequences! (Don't you love warnings like that, too? :-p)
260 protected function encode_site_information(array $info) {
261 return base64_encode(json_encode($info));
265 * Decide if the encoded site information should be sent to the add-ons repository site
267 * For now, we just return true. In the future, we may want to implement some
268 * privacy aware logic (based on site/user preferences for example).
272 protected function should_send_site_info() {
277 * Renames the root directory of the extracted ZIP package.
279 * This method does not validate the presence of the single root directory
280 * (the validator does it later). It just searches for the first directory
281 * under the given location and renames it.
283 * The method will not rename the root if the requested location already
286 * @param string $dirname the location of the extracted ZIP package
287 * @param string $rootdir the requested name of the root directory
288 * @param array $files list of extracted files
289 * @return array eventually amended list of extracted files
291 protected function rename_extracted_rootdir($dirname, $rootdir, array $files) {
293 if (!is_dir($dirname)) {
294 debugging('Unable to rename rootdir of non-existing content', DEBUG_DEVELOPER);
298 if (file_exists($dirname.'/'.$rootdir)) {
299 debugging('Unable to rename rootdir to already existing folder', DEBUG_DEVELOPER);
303 $found = null; // The name of the first subdirectory under the $dirname.
304 foreach (scandir($dirname) as $item) {
305 if (substr($item, 0, 1) === '.') {
308 if (is_dir($dirname.'/'.$item)) {
314 if (!is_null($found)) {
315 if (rename($dirname.'/'.$found, $dirname.'/'.$rootdir)) {
317 foreach ($files as $filepath => $status) {
318 $newpath = preg_replace('~^'.preg_quote($found.'/').'~', preg_quote($rootdir.'/'), $filepath);
319 $newfiles[$newpath] = $status;