From b9e6db9ddcd65c695d94e6df0dcbf467b49f676d Mon Sep 17 00:00:00 2001
From: Juan Leyva <juanleyvadelgado@gmail.com>
Date: Tue, 3 Apr 2018 11:04:39 +0100
Subject: [PATCH] MDL-61449 tool_mobile: New Web Service
 tool_mobile_get_content

---
 admin/tool/mobile/classes/external.php        | 128 ++++++++++++++++++
 admin/tool/mobile/db/services.php             |   9 +-
 admin/tool/mobile/tests/externallib_test.php  |  50 +++++++
 .../mobile/tests/fixtures/output/mobile.php   |  60 ++++++++
 admin/tool/mobile/version.php                 |   2 +-
 5 files changed, 247 insertions(+), 2 deletions(-)
 create mode 100644 admin/tool/mobile/tests/fixtures/output/mobile.php

diff --git a/admin/tool/mobile/classes/external.php b/admin/tool/mobile/classes/external.php
index 398a1a76c86..6a4626477f5 100644
--- a/admin/tool/mobile/classes/external.php
+++ b/admin/tool/mobile/classes/external.php
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
 require_once("$CFG->libdir/externallib.php");
 
 use external_api;
+use external_files;
 use external_function_parameters;
 use external_value;
 use external_single_structure;
@@ -37,6 +38,7 @@ use context_system;
 use moodle_exception;
 use moodle_url;
 use core_text;
+use coding_exception;
 
 /**
  * This is the external API for this tool.
@@ -332,4 +334,130 @@ class external extends external_api {
             )
         );
     }
+
+    /**
+     * Returns description of get_content() parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.5
+     */
+    public static function get_content_parameters() {
+        return new external_function_parameters(
+            array(
+                'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
+                'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
+                'args' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
+                            'value' => new external_value(PARAM_RAW, 'Param value.')
+                        )
+                    ), 'Args for the method are optional.', VALUE_OPTIONAL
+                )
+            )
+        );
+    }
+
+    /**
+     * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
+     * other structured data that will be used to render a view in the Mobile app..
+     *
+     * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
+     * appropriate security checks to access the information to be returned.
+     *
+     * @param string $component fame of the component.
+     * @param string $method function method name in class \$component\output\mobile.
+     * @param array $args optional arguments for the method.
+     * @return array HTML, JavaScript and other required data and information to create a view in the app.
+     * @since Moodle 3.5
+     * @throws coding_exception
+     */
+    public static function get_content($component, $method, $args = array()) {
+        global $OUTPUT, $PAGE, $USER;
+
+        $params = self::validate_parameters(self::get_content_parameters(),
+            array(
+                'component' => $component,
+                'method' => $method,
+                'args' => $args
+            )
+        );
+
+        // Reformat arguments into something less unwieldy.
+        $arguments = array();
+        foreach ($params['args'] as $paramargument) {
+            $arguments[$paramargument['name']] = $paramargument['value'];
+        }
+
+        // The component was validated via the PARAM_COMPONENT parameter type.
+        $classname = '\\' . $params['component'] .'\output\mobile';
+        if (!method_exists($classname, $params['method'])) {
+            throw new coding_exception("Missing method in $classname");
+        }
+        $result = call_user_func_array(array($classname, $params['method']), array($arguments));
+
+        // Populate otherdata.
+        $otherdata = array();
+        if (!empty($result['otherdata'])) {
+            $result['otherdata'] = (array) $result['otherdata'];
+            foreach ($result['otherdata'] as $name => $value) {
+                $otherdata[] = array(
+                    'name' => $name,
+                    'value' => $value
+                );
+            }
+        }
+
+        return array(
+            'templates'  => !empty($result['templates']) ? $result['templates'] : array(),
+            'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
+            'otherdata'  => $otherdata,
+            'files'      => !empty($result['files']) ? $result['files'] : array(),
+            'restrict'   => !empty($result['restrict']) ? $result['restrict'] : array(),
+        );
+    }
+
+    /**
+     * Returns description of get_content() result value
+     *
+     * @return array
+     * @since Moodle 3.5
+     */
+    public static function get_content_returns() {
+        return new external_single_structure(
+            array(
+                'templates' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
+                            'html' => new external_value(PARAM_RAW, 'HTML code.'),
+                        )
+                    ),
+                    'Templates required by the generated content.'
+                ),
+                'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
+                'otherdata' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_RAW, 'Field name.'),
+                            'value' => new external_value(PARAM_RAW, 'Field value.')
+                        )
+                    ),
+                    'Other data that can be used or manipulated by the template via 2-way data-binding.'
+                ),
+                'files' => new external_files('Files in the content.'),
+                'restrict' => new external_single_structure(
+                    array(
+                        'users' => new external_multiple_structure(
+                            new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
+                        ),
+                        'courses' => new external_multiple_structure(
+                            new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
+                        ),
+                    ),
+                    'Restrict this content to certain users or courses.'
+                )
+            )
+        );
+    }
 }
diff --git a/admin/tool/mobile/db/services.php b/admin/tool/mobile/db/services.php
index aa64cc76cdf..5e329c6ad86 100644
--- a/admin/tool/mobile/db/services.php
+++ b/admin/tool/mobile/db/services.php
@@ -60,6 +60,13 @@ $functions = array(
                             Is created only in https sites and is restricted by time and ip address.',
         'type'        => 'write',
         'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
-    )
+    ),
+    'tool_mobile_get_content' => array(
+        'classname'   => 'tool_mobile\external',
+        'methodname'  => 'get_content',
+        'description' => 'Returns a piece of content to be displayed in the Mobile app.',
+        'type'        => 'read',
+        'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
 );
 
diff --git a/admin/tool/mobile/tests/externallib_test.php b/admin/tool/mobile/tests/externallib_test.php
index e61657c5515..0b8873bdcf1 100644
--- a/admin/tool/mobile/tests/externallib_test.php
+++ b/admin/tool/mobile/tests/externallib_test.php
@@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die();
 global $CFG;
 
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/admin/tool/mobile/tests/fixtures/output/mobile.php');
 
 use tool_mobile\external;
 use tool_mobile\api;
@@ -305,4 +306,53 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile'));
         $result = external::get_autologin_key($token->privatetoken);
     }
+
+    /**
+     * Test get_content.
+     */
+    public function test_get_content() {
+
+        $paramval = 16;
+        $result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval)));
+        $result = external_api::clean_returnvalue(external::get_content_returns(), $result);
+        $this->assertCount(1, $result['templates']);
+        $this->assertCount(1, $result['otherdata']);
+        $this->assertCount(2, $result['restrict']['users']);
+        $this->assertCount(2, $result['restrict']['courses']);
+        $this->assertEquals('alert();', $result['javascript']);
+        $this->assertEquals('main', $result['templates'][0]['id']);
+        $this->assertEquals('The HTML code', $result['templates'][0]['html']);
+        $this->assertEquals('otherdata1', $result['otherdata'][0]['name']);
+        $this->assertEquals($paramval, $result['otherdata'][0]['value']);
+        $this->assertEquals(array(1, 2), $result['restrict']['users']);
+        $this->assertEquals(array(3, 4), $result['restrict']['courses']);
+        $this->assertEmpty($result['files']);
+    }
+
+    /**
+     * Test get_content non existent function in valid component.
+     */
+    public function test_get_content_non_existent_function() {
+
+        $this->expectException('coding_exception');
+        $result = external::get_content('tool_mobile', 'test_blahblah');
+    }
+
+    /**
+     * Test get_content incorrect component.
+     */
+    public function test_get_content_invalid_component() {
+
+        $this->expectException('moodle_exception');
+        $result = external::get_content('tool_mobile\hack', 'test_view');
+    }
+
+    /**
+     * Test get_content non existent component.
+     */
+    public function test_get_content_non_existent_component() {
+
+        $this->expectException('moodle_exception');
+        $result = external::get_content('tool_blahblahblah', 'test_view');
+    }
 }
diff --git a/admin/tool/mobile/tests/fixtures/output/mobile.php b/admin/tool/mobile/tests/fixtures/output/mobile.php
new file mode 100644
index 00000000000..d803743f35d
--- /dev/null
+++ b/admin/tool/mobile/tests/fixtures/output/mobile.php
@@ -0,0 +1,60 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Mock class for get_content.
+ *
+ * @package tool_mobile
+ * @copyright 2018 Juan Leyva
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_mobile\output;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Mock class for get_content.
+ *
+ * @package tool_mobile
+ * @copyright 2018 Juan Leyva
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mobile {
+
+    /**
+     * Returns a test view.
+     * @param  array $args Arguments from tool_mobile_get_content WS
+     *
+     * @return array       HTML, javascript and otherdata
+     */
+    public static function test_view($args) {
+        $args = (object) $args;
+
+        return array(
+            'templates' => array(
+                array(
+                    'id' => 'main',
+                    'html' => 'The HTML code',
+                ),
+            ),
+            'javascript' => 'alert();',
+            'otherdata' => array('otherdata1' => $args->param1),
+            'restrict' => array('users' => array(1, 2), 'courses' => array(3, 4)),
+            'files' => array()
+        );
+    }
+}
diff --git a/admin/tool/mobile/version.php b/admin/tool/mobile/version.php
index c09e6fd3b2b..438a6d5f68c 100644
--- a/admin/tool/mobile/version.php
+++ b/admin/tool/mobile/version.php
@@ -23,7 +23,7 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2017111301; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2017110800; // Requires this Moodle version.
 $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
 $plugin->dependencies = array(