MDL-35238 Inform the admin if the update can not be deployed due to write permissions
authorDavid Mudrák <david@moodle.com>
Thu, 8 Nov 2012 23:20:18 +0000 (00:20 +0100)
committerDavid Mudrák <david@moodle.com>
Thu, 8 Nov 2012 23:20:18 +0000 (00:20 +0100)
admin/renderer.php
lang/en/plugin.php
lib/pluginlib.php
mdeploy.php

index 581cdd1..7ca6ea2 100644 (file)
@@ -1206,9 +1206,14 @@ class core_admin_renderer extends plugin_renderer_base {
         $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '');
 
         $deployer = available_update_deployer::instance();
-        if ($deployer->initialized() and $deployer->can_deploy($updateinfo)) {
-            $widget = $deployer->make_confirm_widget($updateinfo);
-            $box .= $this->output->render($widget);
+        if ($deployer->initialized()) {
+            $impediments = $deployer->deployment_impediments($updateinfo);
+            if (empty($impediments)) {
+                $widget = $deployer->make_confirm_widget($updateinfo);
+                $box .= $this->output->render($widget);
+            } else if (isset($impediments['notwritable'])) {
+                $box .= $this->output->help_icon('notwritable', 'core_plugin', get_string('notwritable', 'core_plugin'));
+            }
         }
 
         $box .= $this->output->box_end();
index 6812824..f21667f 100644 (file)
@@ -41,6 +41,10 @@ $string['nonehighlighted'] = 'No plugins require your attention now';
 $string['nonehighlightedinfo'] = 'Display the list of all installed plugins anyway';
 $string['noneinstalled'] = 'No plugins of this type are installed';
 $string['notes'] = 'Notes';
+$string['notwritable'] = 'Plugin files not writable';
+$string['notwritable_help'] = 'You have enabled automatic updates deployment and there is available update for this plugin. However, the plugin files are not writable by the web server so the update can not be installed at the moment.
+
+Make the plugin folder and all its contents writable to be able to install the available update automatically.';
 $string['numtotal'] = 'Installed: {$a}';
 $string['numdisabled'] = 'Disabled: {$a}';
 $string['numextension'] = 'Contributions: {$a}';
index bd7f6d4..e8cb001 100644 (file)
@@ -1538,26 +1538,32 @@ class available_update_deployer {
     }
 
     /**
-     * Check if the available update info contains all required data for deployment.
+     * Returns a list of reasons why the deployment can not happen
      *
-     * All instances of {@link available_update_info} class always provide at least the
-     * component name and component version. Additionally, we also need the URL to download
-     * the ZIP package from and MD5 hash of the ZIP's content.
+     * If the returned array is empty, the deployment seems to be possible. The returned
+     * structure is an associative array with keys representing individual impediments.
+     * Possible keys are: missingdownloadurl, missingdownloadmd5, notwritable.
      *
      * @param available_update_info $info
-     * @return bool
+     * @return array
      */
-    public function can_deploy(available_update_info $info) {
+    public function deployment_impediments(available_update_info $info) {
+
+        $impediments = array();
 
         if (empty($info->download)) {
-            return false;
+            $impediments['missingdownloadurl'] = true;
         }
 
         if (empty($info->downloadmd5)) {
-            return false;
+            $impediments['missingdownloadmd5'] = true;
         }
 
-        return true;
+        if (!$this->component_writable($info->component)) {
+            $impediments['notwritable'] = true;
+        }
+
+        return $impediments;
     }
 
     /**
@@ -1783,7 +1789,6 @@ class available_update_deployer {
         }
     }
 
-
     // End of external API
 
     /**
@@ -1857,6 +1862,70 @@ class available_update_deployer {
     protected function generate_password() {
         return complex_random_string();
     }
+
+    /**
+     * Checks if the given component's directory is writable
+     *
+     * For the purpose of the deployment, the web server process has to have
+     * write access to all files in the component's directory (recursively) and for the
+     * directory itself.
+     *
+     * @see worker::move_directory_source_precheck()
+     * @param string $component normalized component name
+     * @return boolean
+     */
+    protected function component_writable($component) {
+
+        list($plugintype, $pluginname) = normalize_component($component);
+
+        $directory = get_plugin_directory($plugintype, $pluginname);
+
+        if (is_null($directory)) {
+            throw new coding_exception('Unknown component location', $component);
+        }
+
+        return $this->directory_writable($directory);
+    }
+
+    /**
+     * Checks if the directory and all its contents (recursively) is writable
+     *
+     * @param string $path full path to a directory
+     * @return boolean
+     */
+    private function directory_writable($path) {
+
+        if (!is_writable($path)) {
+            return false;
+        }
+
+        if (is_dir($path)) {
+            $handle = opendir($path);
+        } else {
+            return false;
+        }
+
+        $result = true;
+
+        while ($filename = readdir($handle)) {
+            $filepath = $path.'/'.$filename;
+
+            if ($filename === '.' or $filename === '..') {
+                continue;
+            }
+
+            if (is_dir($filepath)) {
+                $result = $result && $this->directory_writable($filepath);
+
+            } else {
+                $result = $result && is_writable($filepath);
+            }
+        }
+
+        closedir($handle);
+
+        return $result;
+    }
 }
 
 
index 8e5bfec..9fba3ba 100644 (file)
@@ -1048,6 +1048,10 @@ class worker extends singleton_pattern {
      */
     protected function move_directory_source_precheck($source) {
 
+        if (!is_writable($source)) {
+            return false;
+        }
+
         if (is_dir($source)) {
             $handle = opendir($source);
         } else {
@@ -1072,7 +1076,8 @@ class worker extends singleton_pattern {
         }
 
         closedir($handle);
-        return $result && is_writable($source);
+
+        return $result;
     }
 
     /**