adac7c1638fb185c95f4e05e1390fb51267ba0a3
[moodle.git] / admin / tool / installaddon / classes / installer.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
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.
9 //
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.
14 //
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/>.
18 /**
19  * Provides tool_installaddon_installer class
20  *
21  * @package     tool_installaddon
22  * @subpackage  classes
23  * @copyright   2013 David Mudrak <david@moodle.com>
24  * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Implements main plugin features.
31  *
32  * @copyright 2013 David Mudrak <david@moodle.com>
33  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class tool_installaddon_installer {
37     /** @var tool_installaddon_installfromzip */
38     protected $installfromzipform = null;
40     /**
41      * Factory method returning an instance of this class.
42      *
43      * @return tool_installaddon_installer
44      */
45     public static function instance() {
46         return new static();
47     }
49     /**
50      * Returns URL to the repository that addons can be searched in and installed from
51      *
52      * @return moodle_url
53      */
54     public function get_addons_repository_url() {
55         global $CFG;
57         if (!empty($CFG->config_php_settings['alternativeaddonsrepositoryurl'])) {
58             $url = $CFG->config_php_settings['alternativeaddonsrepositoryurl'];
59         } else {
60             $url = 'https://moodle.org/plugins/get.php';
61         }
63         if (!$this->should_send_site_info()) {
64             return new moodle_url($url);
65         }
67         // Append the basic information about our site.
68         $site = array(
69             'fullname' => $this->get_site_fullname(),
70             'url' => $this->get_site_url(),
71             'majorversion' => $this->get_site_major_version(),
72         );
74         $site = $this->encode_site_information($site);
76         return new moodle_url($url, array('site' => $site));
77     }
79     /**
80      * @return tool_installaddon_installfromzip
81      */
82     public function get_installfromzip_form() {
83         global $CFG;
84         require_once(dirname(__FILE__).'/installfromzip_form.php');
86         if (!is_null($this->installfromzipform)) {
87             return $this->installfromzipform;
88         }
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;
96     }
98     /**
99      * Saves the ZIP file from the {@link tool_installaddon_installfromzip} form
100      *
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.
103      *
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
107      */
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);
113         return $filename;
114     }
116     /**
117      * Extracts the saved file previously saved by {self::save_installfromzip_file()}
118      *
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.
122      *
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()}
128      */
129     public function extract_installfromzip_file($zipfilepath, $targetdir, $rootdir = '') {
130         global $CFG;
131         require_once($CFG->libdir.'/filelib.php');
133         $fp = get_file_packer('application/zip');
134         $files = $fp->extract_to_pathname($zipfilepath, $targetdir);
136         if ($files) {
137             if (!empty($rootdir)) {
138                 $files = $this->rename_extracted_rootdir($targetdir, $rootdir, $files);
139             }
140             return $files;
142         } else {
143             return array();
144         }
145     }
147     /**
148      * Returns localised list of available plugin types
149      *
150      * @return array (string)plugintype => (string)plugin name
151      */
152     public function get_plugin_types_menu() {
153         global $CFG;
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.')';
161         }
163         return $menu;
164     }
166     /**
167      * Returns the full path of the root of the given plugin type
168      *
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.
171      *
172      * @param string $plugintype
173      * @return string|bool|null
174      */
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;
181                 break;
182             }
183         }
184         if (is_null($plugintypepath)) {
185             return null;
186         }
188         if (!is_dir($plugintypepath)) {
189             return false;
190         }
192         return $plugintypepath;
193     }
195     /**
196      * Is it possible to create a new plugin directory for the given plugin type?
197      *
198      * @throws coding_exception for invalid plugin types or non-existing plugin type locations
199      * @param string $plugintype
200      * @return boolean
201      */
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!');
208         }
210         if ($plugintypepath === false) {
211             throw new coding_exception('Plugin type location does not exist!');
212         }
214         return is_writable($plugintypepath);
215     }
217     //// End of external API ///////////////////////////////////////////////////
219     /**
220      * @see self::instance()
221      */
222     protected function __construct() {
223     }
225     /**
226      * @return string this site full name
227      */
228     protected function get_site_fullname() {
229         global $SITE;
231         return strip_tags($SITE->fullname);
232     }
234     /**
235      * @return string this site URL
236      */
237     protected function get_site_url() {
238         global $CFG;
240         return $CFG->wwwroot;
241     }
243     /**
244      * @return string major version like 2.5, 2.6 etc.
245      */
246     protected function get_site_major_version() {
247         return moodle_major_version();
248     }
250     /**
251      * Encodes the given array in a way that can be safely appended as HTTP GET param
252      *
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)
256      *
257      * @param array $info
258      * @return string
259      */
260     protected function encode_site_information(array $info) {
261         return base64_encode(json_encode($info));
262     }
264     /**
265      * Decide if the encoded site information should be sent to the add-ons repository site
266      *
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).
269      *
270      * @return bool
271      */
272     protected function should_send_site_info() {
273         return true;
274     }
276     /**
277      * Renames the root directory of the extracted ZIP package.
278      *
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.
282      *
283      * The method will not rename the root if the requested location already
284      * exists.
285      *
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
290      */
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);
295             return $files;
296         }
298         if (file_exists($dirname.'/'.$rootdir)) {
299             debugging('Unable to rename rootdir to already existing folder', DEBUG_DEVELOPER);
300             return $files;
301         }
303         $found = null; // The name of the first subdirectory under the $dirname.
304         foreach (scandir($dirname) as $item) {
305             if (substr($item, 0, 1) === '.') {
306                 continue;
307             }
308             if (is_dir($dirname.'/'.$item)) {
309                 $found = $item;
310                 break;
311             }
312         }
314         if (!is_null($found)) {
315             if (rename($dirname.'/'.$found, $dirname.'/'.$rootdir)) {
316                 $newfiles = array();
317                 foreach ($files as $filepath => $status) {
318                     $newpath = preg_replace('~^'.preg_quote($found.'/').'~', preg_quote($rootdir.'/'), $filepath);
319                     $newfiles[$newpath] = $status;
320                 }
321                 return $newfiles;
322             }
323         }
325         return $files;
326     }