Browse files

MDL-35238 Introduce available_update_deployer class

This class represents the communication bridge from Moodle UI to the
(standalone) mdeploy.php utility. It consists of various helper methods
useful when dealing with user interface, update confirmation etc.

The class is implemented as a singleton. This allows us easily
transfer required data from top level scripts (like /admin/index.php)
into the rendering methods deep in the stack without the need to change
the API of many methods on the way.
  • Loading branch information...
1 parent f965e16 commit 7683e550ac50721ed3bea202d8ec0cbe88975d85 @mudrd8mz mudrd8mz committed Oct 7, 2012
Showing with 343 additions and 0 deletions.
  1. +1 −0 lang/en/admin.php
  2. +328 −0 lib/pluginlib.php
  3. +14 −0 lib/tests/pluginlib_test.php
View
1 lang/en/admin.php
@@ -1005,6 +1005,7 @@
$string['updateavailable_moreinfo'] = 'More info...';
$string['updateavailable_release'] = 'Moodle {$a}';
$string['updateavailable_version'] = 'Version {$a}';
+$string['updateavailableinstall'] = 'Install this update';
$string['updateavailablenot'] = 'Your Moodle code is up-to-date!';
$string['updatenotifications'] = 'Update notifications';
$string['updatenotificationfooter'] = 'Your Moodle site {$a->siteurl} is configured to automatically check for available updates. You are receiving this message as the administrator of the site. You can disable automatic checks for available updates in the Site administration section of the Settings block. You can customize the delivery of this message via your personal Messaging setting in the My profile settings section.';
View
328 lib/pluginlib.php
@@ -1429,6 +1429,334 @@ public function __construct($name, array $info) {
/**
+ * Implements a communication bridge to the mdeploy.php utility
+ */
+class available_update_deployer {
+
+ const HTTP_PARAM_PREFIX = 'updteautodpldata_'; // Hey, even Google has not heard of such a prefix! So it MUST be safe :-p
+ const HTTP_PARAM_CHECKER = 'datapackagesize'; // Name of the parameter that holds the number of items in the received data items
+
+ /** @var available_update_deployer holds the singleton instance */
+ protected static $singletoninstance;
+ /** @var moodle_url URL of a page that includes the deployer UI */
+ protected $callerurl;
+ /** @var moodle_url URL to return after the deployment */
+ protected $returnurl;
+
+ /**
+ * Direct instantiation not allowed, use the factory method {@link self::instance()}
+ */
+ protected function __construct() {
+ }
+
+ /**
+ * Sorry, this is singleton
+ */
+ protected function __clone() {
+ }
+
+ /**
+ * Factory method for this class
+ *
+ * @return available_update_deployer the singleton instance
+ */
+ public static function instance() {
+ if (is_null(self::$singletoninstance)) {
+ self::$singletoninstance = new self();
+ }
+ return self::$singletoninstance;
+ }
+
+ /**
+ * Is automatic deployment enabled?
+ *
+ * @return bool
+ */
+ public function enabled() {
+ global $CFG;
+
+ if (!empty($CFG->disableupdateautodeploy)) {
+ // The feature is prohibited via config.php
+ return false;
+ }
+
+ return get_config('updateautodeploy');
+ }
+
+ /**
+ * Sets some base properties of the class to make it usable.
+ *
+ * @param moodle_url $callerurl the base URL of a script that will handle the class'es form data
+ * @param moodle_url $returnurl the final URL to return to when the deployment is finished
+ */
+ public function initialize(moodle_url $callerurl, moodle_url $returnurl) {
+
+ if (!$this->enabled()) {
+ throw new coding_exception('Unable to initialize the deployer, the feature is not enabled.');
+ }
+
+ $this->callerurl = $callerurl;
+ $this->returnurl = $returnurl;
+ }
+
+ /**
+ * Has the deployer been initialized?
+ *
+ * Initialized deployer means that the following properties were set:
+ * callerurl, returnurl
+ *
+ * @return bool
+ */
+ public function initialized() {
+
+ if (!$this->enabled()) {
+ return false;
+ }
+
+ if (empty($this->callerurl)) {
+ return false;
+ }
+
+ if (empty($this->returnurl)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the available update info contains all required data for deployment.
+ *
+ * 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.
+ *
+ * @param available_update_info $info
+ * @return bool
+ */
+ public function can_deploy(available_update_info $info) {
+
+ if (empty($info->download)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Prepares a renderable widget to confirm installation of an available update.
+ *
+ * @param available_update_info $info component version to deploy
+ * @return renderable
+ */
+ public function make_confirm_widget(available_update_info $info) {
+
+ if (!$this->initialized()) {
+ throw new coding_exception('Illegal method call - deployer not initialized.');
+ }
+
+ $params = $this->data_to_params(array(
+ 'updateinfo' => (array)$info, // see http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
+ ));
+
+ $widget = new single_button(
+ new moodle_url($this->callerurl, $params),
+ get_string('updateavailableinstall', 'core_admin'),
+ 'post'
+ );
+
+ return $widget;
+ }
+
+ /**
+ * Prepares a renderable widget to execute installation of an available update.
+ *
+ * @param available_update_info $info component version to deploy
+ * @return renderable
+ */
+ public function make_execution_widget(available_update_info $info) {
+ global $CFG;
+
+ if (!$this->initialized()) {
+ throw new coding_exception('Illegal method call - deployer not initialized.');
+ }
+
+ $pluginrootpaths = get_plugin_types(true);
+
+ list($plugintype, $pluginname) = normalize_component($info->component);
+
+ if (empty($pluginrootpaths[$plugintype])) {
+ throw new coding_exception('Unknown plugin type root location', $plugintype);
+ }
+
+ $params = array(
+ 'upgrade' => true,
+ 'type' => $plugintype,
+ 'name' => $pluginname,
+ 'typeroot' => $pluginrootpaths[$plugintype],
+ 'download' => $info->download,
+ 'dataroot' => $CFG->dataroot,
+ 'dirroot' => $CFG->dirroot,
+ 'passfile' => '', // TODO
+ 'password' => '', // TODO
+ );
+
+ $widget = new single_button(
+ new moodle_url('/mdeploy.php', $params),
+ get_string('updateavailableinstall', 'core_admin'),
+ 'post'
+ );
+
+ return $widget;
+ }
+
+ /**
+ * Returns array of data objects passed to this tool.
+ *
+ * @return array
+ */
+ public function submitted_data() {
+
+ $data = $this->params_to_data($_POST);
+
+ if (empty($data) or empty($data[self::HTTP_PARAM_CHECKER])) {
+ return false;
+ }
+
+ if (!empty($data['updateinfo']) and is_object($data['updateinfo'])) {
+ $updateinfo = $data['updateinfo'];
+ if (!empty($updateinfo->component) and !empty($updateinfo->version)) {
+ $data['updateinfo'] = new available_update_info($updateinfo->component, (array)$updateinfo);
+ }
+ }
+
+ if (!empty($data['callerurl'])) {
+ $data['callerurl'] = new moodle_url($data['callerurl']);
+ }
+
+ if (!empty($data['returnurl'])) {
+ $data['returnurl'] = new moodle_url($data['returnurl']);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Handles magic getters and setters for protected properties.
+ *
+ * @param string $name method name, e.g. set_returnurl()
+ * @param array $arguments arguments to be passed to the array
+ */
+ public function __call($name, array $arguments = array()) {
+
+ if (substr($name, 0, 4) === 'set_') {
+ $property = substr($name, 4);
+ if (empty($property)) {
+ throw new coding_exception('Invalid property name (empty)');
+ }
+ if (empty($arguments)) {
+ $arguments = array(true); // Default value for flag-like properties.
+ }
+ // Make sure it is a protected property.
+ $isprotected = false;
+ $reflection = new ReflectionObject($this);
+ foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $reflectionproperty) {
+ if ($reflectionproperty->getName() === $property) {
+ $isprotected = true;
+ break;
+ }
+ }
+ if (!$isprotected) {
+ throw new coding_exception('Unable to set property - it does not exist or it is not protected');
+ }
+ $value = reset($arguments);
+ $this->$property = $value;
+ return;
+ }
+
+ if (substr($name, 0, 4) === 'get_') {
+ $property = substr($name, 4);
+ if (empty($property)) {
+ throw new coding_exception('Invalid property name (empty)');
+ }
+ if (!empty($arguments)) {
+ throw new coding_exception('No parameter expected');
+ }
+ // Make sure it is a protected property.
+ $isprotected = false;
+ $reflection = new ReflectionObject($this);
+ foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $reflectionproperty) {
+ if ($reflectionproperty->getName() === $property) {
+ $isprotected = true;
+ break;
+ }
+ }
+ if (!$isprotected) {
+ throw new coding_exception('Unable to get property - it does not exist or it is not protected');
+ }
+ return $this->$property;
+ }
+ }
+
+ // End of external API
+
+ /**
+ * Prepares an array of HTTP parameters that can be passed to another page.
+ *
+ * @param array|object $data associative array or an object holding the data, data JSON-able
+ * @return array suitable as a param for moodle_url
+ */
+ protected function data_to_params($data) {
+
+ // Append some our own data
+ if (!empty($this->callerurl)) {
+ $data['callerurl'] = $this->callerurl->out(false);
+ }
+ if (!empty($this->callerurl)) {
+ $data['returnurl'] = $this->returnurl->out(false);
+ }
+
+ // Finally append the count of items in the package.
+ $data[self::HTTP_PARAM_CHECKER] = count($data);
+
+ // Generate params
+ $params = array();
+ foreach ($data as $name => $value) {
+ $transname = self::HTTP_PARAM_PREFIX.$name;
+ $transvalue = json_encode($value);
+ $params[$transname] = $transvalue;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Converts HTTP parameters passed to the script into native PHP data
+ *
+ * @param array $params such as $_REQUEST or $_POST
+ * @return array data passed for this class
+ */
+ protected function params_to_data(array $params) {
+
+ if (empty($params)) {
+ return array();
+ }
+
+ $data = array();
+ foreach ($params as $name => $value) {
+ if (strpos($name, self::HTTP_PARAM_PREFIX) === 0) {
+ $realname = substr($name, strlen(self::HTTP_PARAM_PREFIX));
+ $realvalue = json_decode($value);
+ $data[$realname] = $realvalue;
+ }
+ }
+
+ return $data;
+ }
+}
+
+
+/**
* Factory class producing required subclasses of {@link plugininfo_base}
*/
class plugininfo_default_factory {
View
14 lib/tests/pluginlib_test.php
@@ -543,3 +543,17 @@ protected function cron_execute() {
class testable_available_update_checker_cron_executed extends Exception {
}
+
+
+/**
+ * Test cases for {@link available_update_deployer} class
+ */
+class available_update_deployer_test extends advanced_testcase {
+
+ public function test_magic_setters() {
+ $deployer = available_update_deployer::instance();
+ $value = new moodle_url('/');
+ $deployer->set_returnurl($value);
+ $this->assertSame($deployer->get_returnurl(), $value);
+ }
+}

0 comments on commit 7683e55

Please sign in to comment.