MDL-66388 h5p: add h5p atto button
authorBas Brands <bas@moodle.com>
Tue, 20 Aug 2019 14:22:45 +0000 (16:22 +0200)
committerBas Brands <bas@moodle.com>
Mon, 23 Sep 2019 14:32:23 +0000 (16:32 +0200)
24 files changed:
.eslintignore
.stylelintignore
lib/classes/plugin_manager.php
lib/editor/atto/db/upgrade.php
lib/editor/atto/plugins/h5p/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/h5p/db/access.php [new file with mode: 0644]
lib/editor/atto/plugins/h5p/js/h5p-resizer.js [new file with mode: 0644]
lib/editor/atto/plugins/h5p/js/readme_moodle.txt [new file with mode: 0644]
lib/editor/atto/plugins/h5p/lang/en/atto_h5p.php [new file with mode: 0644]
lib/editor/atto/plugins/h5p/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/h5p/pix/icon.png [new file with mode: 0644]
lib/editor/atto/plugins/h5p/pix/icon.svg [new file with mode: 0644]
lib/editor/atto/plugins/h5p/styles.css [new file with mode: 0644]
lib/editor/atto/plugins/h5p/tests/behat/h5p.feature [new file with mode: 0644]
lib/editor/atto/plugins/h5p/thirdpartylibs.xml [new file with mode: 0644]
lib/editor/atto/plugins/h5p/version.php [new file with mode: 0644]
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js [new file with mode: 0644]
lib/editor/atto/plugins/h5p/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/h5p/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/h5p/yui/src/button/meta/button.json [new file with mode: 0644]
lib/editor/atto/settings.php
lib/editor/atto/version.php

index fe02a73..4689ff0 100644 (file)
@@ -9,6 +9,7 @@ cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
+lib/editor/atto/plugins/h5p/js/h5p-resizer.js
 lib/editor/atto/plugins/html/yui/src/codemirror/
 lib/editor/atto/plugins/html/yui/src/beautify/
 lib/editor/atto/yui/src/rangy/js/*.*
index d3e4974..b8cd2a8 100644 (file)
@@ -10,6 +10,7 @@ cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
+lib/editor/atto/plugins/h5p/js/h5p-resizer.js
 lib/editor/atto/plugins/html/yui/src/codemirror/
 lib/editor/atto/plugins/html/yui/src/beautify/
 lib/editor/atto/yui/src/rangy/js/*.*
index 960cdf7..1e65d4f 100644 (file)
@@ -1687,7 +1687,7 @@ class core_plugin_manager {
                 'equation', 'fontcolor', 'html', 'image', 'indent', 'italic',
                 'link', 'managefiles', 'media', 'noautolink', 'orderedlist',
                 'recordrtc', 'rtl', 'strike', 'subscript', 'superscript', 'table',
-                'title', 'underline', 'undo', 'unorderedlist'
+                'title', 'underline', 'undo', 'unorderedlist', 'h5p'
             ),
 
             'assignment' => array(
index 7b1f233..7986689 100644 (file)
@@ -103,5 +103,28 @@ function xmldb_editor_atto_upgrade($oldversion) {
     // Automatically generated Moodle v3.7.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2019090900) {
+        $toolbar = get_config('editor_atto', 'toolbar');
+
+        if (strpos($toolbar, 'h5p') === false) {
+            $glue = "\r\n";
+            if (strpos($toolbar, $glue) === false) {
+                $glue = "\n";
+            }
+            $groups = explode($glue, $toolbar);
+            // Try to put h5p in the files group.
+            foreach ($groups as $i => $group) {
+                $parts = explode('=', $group);
+                if (trim($parts[0]) == 'files') {
+                    $groups[$i] = 'files = ' . trim($parts[1]) . ', h5p';
+                    // Update config variable.
+                    $toolbar = implode($glue, $groups);
+                    set_config('toolbar', $toolbar, 'editor_atto');
+                }
+            }
+        }
+        // Atto editor savepoint reached.
+        upgrade_plugin_savepoint(true, 2019090900, 'editor', 'atto');
+    }
     return true;
 }
diff --git a/lib/editor/atto/plugins/h5p/classes/privacy/provider.php b/lib/editor/atto/plugins/h5p/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..e2b5aeb
--- /dev/null
@@ -0,0 +1,46 @@
+<?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/>.
+
+/**
+ * Privacy Subsystem implementation for atto_h5p.
+ *
+ * @package    atto_h5p
+ * @copyright  2019 Bas Brands <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace atto_h5p\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for atto_h5p implementing null_provider.
+ *
+ * @copyright  2019 Bas Brands <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
diff --git a/lib/editor/atto/plugins/h5p/db/access.php b/lib/editor/atto/plugins/h5p/db/access.php
new file mode 100644 (file)
index 0000000..76bde79
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * H5P Atto button capabilities.
+ *
+ * @package    atto_h5p
+ * @copyright  2019 Bas Brands <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$capabilities = [
+    'atto/h5p:addembed' => [
+        'captype'      => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => [
+            'editingteacher' => CAP_ALLOW,
+        ],
+    ]
+];
diff --git a/lib/editor/atto/plugins/h5p/js/h5p-resizer.js b/lib/editor/atto/plugins/h5p/js/h5p-resizer.js
new file mode 100644 (file)
index 0000000..ed78724
--- /dev/null
@@ -0,0 +1,131 @@
+// H5P iframe Resizer
+(function () {
+  if (!window.postMessage || !window.addEventListener || window.h5pResizerInitialized) {
+    return; // Not supported
+  }
+  window.h5pResizerInitialized = true;
+
+  // Map actions to handlers
+  var actionHandlers = {};
+
+  /**
+   * Prepare iframe resize.
+   *
+   * @private
+   * @param {Object} iframe Element
+   * @param {Object} data Payload
+   * @param {Function} respond Send a response to the iframe
+   */
+  actionHandlers.hello = function (iframe, data, respond) {
+    // Make iframe responsive
+    iframe.style.width = '100%';
+
+    // Bugfix for Chrome: Force update of iframe width. If this is not done the
+    // document size may not be updated before the content resizes.
+    iframe.getBoundingClientRect();
+
+    // Tell iframe that it needs to resize when our window resizes
+    var resize = function () {
+      if (iframe.contentWindow) {
+        // Limit resize calls to avoid flickering
+        respond('resize');
+      }
+      else {
+        // Frame is gone, unregister.
+        window.removeEventListener('resize', resize);
+      }
+    };
+    window.addEventListener('resize', resize, false);
+
+    // Respond to let the iframe know we can resize it
+    respond('hello');
+  };
+
+  /**
+   * Prepare iframe resize.
+   *
+   * @private
+   * @param {Object} iframe Element
+   * @param {Object} data Payload
+   * @param {Function} respond Send a response to the iframe
+   */
+  actionHandlers.prepareResize = function (iframe, data, respond) {
+    // Do not resize unless page and scrolling differs
+    if (iframe.clientHeight !== data.scrollHeight ||
+        data.scrollHeight !== data.clientHeight) {
+
+      // Reset iframe height, in case content has shrinked.
+      iframe.style.height = data.clientHeight + 'px';
+      respond('resizePrepared');
+    }
+  };
+
+  /**
+   * Resize parent and iframe to desired height.
+   *
+   * @private
+   * @param {Object} iframe Element
+   * @param {Object} data Payload
+   * @param {Function} respond Send a response to the iframe
+   */
+  actionHandlers.resize = function (iframe, data) {
+    // Resize iframe so all content is visible. Use scrollHeight to make sure we get everything
+    iframe.style.height = data.scrollHeight + 'px';
+  };
+
+  /**
+   * Keyup event handler. Exits full screen on escape.
+   *
+   * @param {Event} event
+   */
+  var escape = function (event) {
+    if (event.keyCode === 27) {
+      exitFullScreen();
+    }
+  };
+
+  // Listen for messages from iframes
+  window.addEventListener('message', function receiveMessage(event) {
+    if (event.data.context !== 'h5p') {
+      return; // Only handle h5p requests.
+    }
+
+    // Find out who sent the message
+    var iframe, iframes = document.getElementsByTagName('iframe');
+    for (var i = 0; i < iframes.length; i++) {
+      if (iframes[i].contentWindow === event.source) {
+        iframe = iframes[i];
+        break;
+      }
+    }
+
+    if (!iframe) {
+      return; // Cannot find sender
+    }
+
+    // Find action handler handler
+    if (actionHandlers[event.data.action]) {
+      actionHandlers[event.data.action](iframe, event.data, function respond(action, data) {
+        if (data === undefined) {
+          data = {};
+        }
+        data.action = action;
+        data.context = 'h5p';
+        event.source.postMessage(data, event.origin);
+      });
+    }
+  }, false);
+
+  // Let h5p iframes know we're ready!
+  var iframes = document.getElementsByTagName('iframe');
+  var ready = {
+    context: 'h5p',
+    action: 'ready'
+  };
+  for (var i = 0; i < iframes.length; i++) {
+    if (iframes[i].src.indexOf('h5p') !== -1) {
+      iframes[i].contentWindow.postMessage(ready, '*');
+    }
+  }
+
+})();
diff --git a/lib/editor/atto/plugins/h5p/js/readme_moodle.txt b/lib/editor/atto/plugins/h5p/js/readme_moodle.txt
new file mode 100644 (file)
index 0000000..e403b44
--- /dev/null
@@ -0,0 +1,9 @@
+The H5P resizer JS.
+
+to update:
+
+Downloaded last release from: https://github.com/h5p/h5p-php-library/releases
+
+Import
+
+- In the downloaded h5p-php-library copy js/h5p-resizer.js into lib/editor/atto/plugins/h5p/js
diff --git a/lib/editor/atto/plugins/h5p/lang/en/atto_h5p.php b/lib/editor/atto/plugins/h5p/lang/en/atto_h5p.php
new file mode 100644 (file)
index 0000000..2343281
--- /dev/null
@@ -0,0 +1,31 @@
+<?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/>.
+
+/**
+ * Strings for component 'atto_h5p', language 'en'.
+ *
+ * @package    atto_h5p
+ * @copyright  2019 Bas Brands <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['enterurl'] = 'Enter URL';
+$string['h5pproperties'] = 'H5P properties';
+$string['invalidh5purl'] = 'Invalid URL';
+$string['pluginname'] = 'Insert H5P';
+$string['privacy:metadata'] = 'The atto_h5p plugin does not store any personal data.';
+$string['h5p:addembed'] = 'Add embedded H5P';
+$string['saveh5p'] = 'Save H5P';
\ No newline at end of file
diff --git a/lib/editor/atto/plugins/h5p/lib.php b/lib/editor/atto/plugins/h5p/lib.php
new file mode 100644 (file)
index 0000000..d415bc0
--- /dev/null
@@ -0,0 +1,67 @@
+<?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/>.
+
+/**
+ * Atto text editor integration version file.
+ *
+ * @package    atto_h5p
+ * @copyright  2019 Bas Brands <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Set params for this button.
+ *
+ * @param string $elementid
+ * @param stdClass $options - the options for the editor, including the context.
+ * @param stdClass $fpoptions - unused.
+ */
+function atto_h5p_params_for_js($elementid, $options, $fpoptions) {
+    $context = $options['context'];
+    if (!$context) {
+        $context = context_system::instance();
+    }
+    $addembed = has_capability('atto/h5p:addembed', $context);
+
+    $allowedmethods = 'none';
+    if ($addembed) {
+        $allowedmethods = 'embed';
+    }
+
+    $params = ['allowedmethods' => $allowedmethods];
+    return $params;
+}
+
+/**
+ * Initialise the strings required for js
+ */
+function atto_h5p_strings_for_js() {
+    global $PAGE;
+
+    $strings = array(
+        'saveh5p',
+        'h5pproperties',
+        'enterurl',
+        'invalidh5purl'
+    );
+
+    $PAGE->requires->strings_for_js($strings, 'atto_h5p');
+    $PAGE->requires->js(new moodle_url('/lib/editor/atto/plugins/h5p/js/h5p-resizer.js'));
+}
+
+
diff --git a/lib/editor/atto/plugins/h5p/pix/icon.png b/lib/editor/atto/plugins/h5p/pix/icon.png
new file mode 100644 (file)
index 0000000..51a640d
Binary files /dev/null and b/lib/editor/atto/plugins/h5p/pix/icon.png differ
diff --git a/lib/editor/atto/plugins/h5p/pix/icon.svg b/lib/editor/atto/plugins/h5p/pix/icon.svg
new file mode 100644 (file)
index 0000000..7856f9e
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        viewBox="0 0 345 150" style="enable-background:new 0 0 345 150;" xml:space="preserve">
+<g>
+       <path d="M325.7,14.7C317.6,6.9,305.3,3,289,3h-43.5H234v31h-66l-5.4,22.2c4.5-2.1,10.9-4.2,15.3-5.3c4.4-1.1,8.8-0.9,13.1-0.9
+               c14.6,0,26.5,4.5,35.6,13.3c9.1,8.8,13.6,20,13.6,33.4c0,9.4-2.3,18.5-7,27.2s-11.3,15.4-19.9,20c-3.1,1.6-6.5,3.1-10.2,4.1h42.4
+               H259V95h25c18.2,0,31.7-4.2,40.6-12.5s13.3-19.9,13.3-34.6C337.9,33.6,333.8,22.5,325.7,14.7z M288.7,60.6c-3.5,3-9.6,4.4-18.3,4.4
+               H259V33h13.2c8.4,0,14.2,1.5,17.2,4.7c3.1,3.2,4.6,6.9,4.6,11.5C294,53.9,292.2,57.6,288.7,60.6z"/>
+       <path d="M176.5,76.3c-7.9,0-14.7,4.6-18,11.2L119,81.9L136.8,3h-23.6H101v62H51V3H7v145h44V95h50v53h12.2h42
+               c-6.7-2-12.5-4.6-17.2-8.1c-4.8-3.6-8.7-7.7-11.7-12.3c-3-4.6-5.3-9.7-7.3-16.5l39.6-5.7c3.3,6.6,10.1,11.1,17.9,11.1
+               c11.1,0,20.1-9,20.1-20.1S187.5,76.3,176.5,76.3z"/>
+</g>
+</svg>
diff --git a/lib/editor/atto/plugins/h5p/styles.css b/lib/editor/atto/plugins/h5p/styles.css
new file mode 100644 (file)
index 0000000..c808561
--- /dev/null
@@ -0,0 +1,17 @@
+.attoh5poverlay {
+    display: none;
+}
+.editor_atto_content_wrap .attoh5poverlay {
+    display: block;
+    position: absolute;
+    cursor: pointer;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    height: 100%;
+    background: url([[pix:atto_h5p|icon]]) center center / 100px auto no-repeat #adb5bd;
+}
+.h5p-embed-placeholder .attoh5poverlay + br {
+    display: none;
+}
\ No newline at end of file
diff --git a/lib/editor/atto/plugins/h5p/tests/behat/h5p.feature b/lib/editor/atto/plugins/h5p/tests/behat/h5p.feature
new file mode 100644 (file)
index 0000000..1d2c920
--- /dev/null
@@ -0,0 +1,56 @@
+@editor @editor_atto @atto @atto_h5p @_switch_iframe
+Feature: Add h5ps to Atto
+  To write rich text - I need to add h5ps.
+
+  Background:
+    Given the following "courses" exist:
+      | shortname | fullname |
+      | C1        | Course 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And the following "activities" exist:
+      | activity | name       | intro      | introformat | course | content  | contentformat | idnumber |
+      | page     | PageName1  | PageDesc1  | 1           | C1     | H5Ptest  | 1             | 1        |
+
+  @javascript
+  Scenario: Insert an embedded h5p
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+    And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
+    And I set the field "Enter URL" to "https://h5p.org/h5p/embed/576651"
+    And I click on "Save H5P" "button" in the "H5P properties" "dialogue"
+    And I wait until the page is ready
+    When I click on "Save and display" "button"
+    And I switch to "h5pcontent" iframe
+    Then ".h5p-iframe" "css_element" should exist
+
+  @javascript
+  Scenario: Test an invalid url
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+    And I click on "Insert H5P" "button" in the "#fitem_id_page" "css_element"
+    And I set the field "Enter URL" to "ftp://h5p.org/h5p/embed/576651"
+    And I click on "Save H5P" "button" in the "H5P properties" "dialogue"
+    And I wait until the page is ready
+    Then I should see "Invalid URL" in the "H5P properties" "dialogue"
+
+  @javascript
+  Scenario: No embed h5p capabilities
+    Given I log in as "admin"
+    And I set the following system permissions of "Teacher" role:
+    | capability | permission |
+    | atto/h5p:addembed | Prohibit |
+    And I log out
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+    Then "Insert H5P" "button" should not exist
\ No newline at end of file
diff --git a/lib/editor/atto/plugins/h5p/thirdpartylibs.xml b/lib/editor/atto/plugins/h5p/thirdpartylibs.xml
new file mode 100644 (file)
index 0000000..e7f5237
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<libraries>
+  <library>
+    <location>js/h5p-resizer.js</location>
+    <name>H5P Resizer</name>
+    <license>GPL-3.0</license>
+    <version>1.23.1</version>
+    <licenseversion></licenseversion>
+  </library>
+</libraries>
\ No newline at end of file
diff --git a/lib/editor/atto/plugins/h5p/version.php b/lib/editor/atto/plugins/h5p/version.php
new file mode 100644 (file)
index 0000000..62077db
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/>.
+
+/**
+ * Atto text editor integration version file.
+ *
+ * @package    atto_h5p
+ * @copyright  2019 Bas Brands  <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2019081900;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2019051100;        // Requires this Moodle version.
+$plugin->component = 'atto_h5p';  // Full name of the plugin (used for diagnostics).
diff --git a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js
new file mode 100644 (file)
index 0000000..7defc49
Binary files /dev/null and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js differ
diff --git a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js
new file mode 100644 (file)
index 0000000..ecb5221
Binary files /dev/null and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js differ
diff --git a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js
new file mode 100644 (file)
index 0000000..7defc49
Binary files /dev/null and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js differ
diff --git a/lib/editor/atto/plugins/h5p/yui/src/button/build.json b/lib/editor/atto/plugins/h5p/yui/src/button/build.json
new file mode 100644 (file)
index 0000000..a7d3bda
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "name": "moodle-atto_h5p-button",
+  "builds": {
+    "moodle-atto_h5p-button": {
+      "jsfiles": [
+        "button.js"
+      ]
+    }
+  }
+}
diff --git a/lib/editor/atto/plugins/h5p/yui/src/button/js/button.js b/lib/editor/atto/plugins/h5p/yui/src/button/js/button.js
new file mode 100644 (file)
index 0000000..8ba6225
--- /dev/null
@@ -0,0 +1,315 @@
+// 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/>.
+
+/*
+ * @package    atto_h5p
+ * @copyright  2019 Bas Brands  <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * @module moodle-atto_h5p-button
+ */
+
+/**
+ * Atto h5p content tool.
+ *
+ * @namespace M.atto_h5p
+ * @class Button
+ * @extends M.editor_atto.EditorPlugin
+ */
+
+var CSS = {
+        INPUTALT: 'atto_h5p_altentry',
+        INPUTSUBMIT: 'atto_h5p_urlentrysubmit',
+        INPUTH5PURL: 'atto_h5p_url',
+        URLWARNING: 'atto_h5p_warning'
+    },
+    SELECTORS = {
+        INPUTH5PURL: '.' + CSS.INPUTH5PURL
+    },
+
+    COMPONENTNAME = 'atto_h5p',
+
+    TEMPLATE = '' +
+            '<form class="atto_form">' +
+                '<div class="mb-4">' +
+                    '<label for="{{elementid}}_{{CSS.INPUTH5PURL}}">{{get_string "enterurl" component}}</label>' +
+                    '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.URLWARNING}}">' +
+                        '{{get_string "invalidh5purl" component}}' +
+                    '</div>' +
+                    '<input class="form-control fullwidth {{CSS.INPUTH5PURL}}" type="url" ' +
+                    'id="{{elementid}}_{{CSS.INPUTH5PURL}}" size="32"/>' +
+                '</div>' +
+                '<div class="text-center">' +
+                '<button class="btn btn-secondary {{CSS.INPUTSUBMIT}}" type="submit">' + '' +
+                    '{{get_string "saveh5p" component}}</button>' +
+                '</div>' +
+            '</form>',
+
+        H5PTEMPLATE = '' +
+            '<div class="position-relative h5p-embed-placeholder">' +
+                '<div class="attoh5poverlay"></div>' +
+                '<iframe id="h5pcontent" class="h5pcontent" src="{{url}}/embed" ' +
+                    'width="100%" height="637" frameborder="0"' +
+                    'allowfullscreen="{{allowfullscreen}}" allowmedia="{{allowmedia}}">' +
+                '</iframe>' +
+                '<script src="' + M.cfg.wwwroot + '/lib/editor/atto/plugins/h5p/js/h5p-resizer.js"' +
+                    'charset="UTF-8"></script>' +
+                '</div>' +
+            '</div>' +
+            '<p><br></p>';
+
+Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
+    /**
+     * A reference to the current selection at the time that the dialogue
+     * was opened.
+     *
+     * @property _currentSelection
+     * @type Range
+     * @private
+     */
+    _currentSelection: null,
+
+    /**
+     * A reference to the currently open form.
+     *
+     * @param _form
+     * @type Node
+     * @private
+     */
+    _form: null,
+
+    /**
+     * A reference to the currently selected H5P placeholder.
+     *
+     * @param _form
+     * @type Node
+     * @private
+     */
+    _placeholderH5P: null,
+
+    initializer: function() {
+        var allowedmethods = this.get('allowedmethods');
+        if (allowedmethods !== 'embed') {
+            // Plugin not available here.
+            return;
+        }
+
+        this.addButton({
+            icon: 'icon',
+            iconComponent: 'atto_h5p',
+            callback: this._displayDialogue,
+            tags: '.attoh5poverlay',
+            tagMatchRequiresAll: false
+        });
+
+        this.editor.delegate('dblclick', this._handleDblClick, '.attoh5poverlay', this);
+        this.editor.delegate('click', this._handleClick, '.attoh5poverlay', this);
+    },
+
+    /**
+     * Handle a double click on a H5P Placeholder.
+     *
+     * @method _handleDblClick
+     * @private
+     */
+    _handleDblClick: function() {
+        this._displayDialogue();
+    },
+
+    /**
+     * Handle a click on a H5P Placeholder.
+     *
+     * @method _handleClick
+     * @param {EventFacade} e
+     * @private
+     */
+    _handleClick: function(e) {
+        var h5pplaceholder = e.target;
+
+        var selection = this.get('host').getSelectionFromNode(h5pplaceholder);
+        if (this.get('host').getSelection() !== selection) {
+            this.get('host').setSelection(selection);
+        }
+    },
+
+    /**
+     * Display the h5p editing tool.
+     *
+     * @method _displayDialogue
+     * @private
+     */
+    _displayDialogue: function() {
+        // Store the current selection.
+        this._currentSelection = this.get('host').getSelection();
+        this._placeholderH5P = this._getH5PIframe();
+
+        if (this._currentSelection === false) {
+            return;
+        }
+        var dialogue = this.getDialogue({
+            headerContent: M.util.get_string('h5pproperties', COMPONENTNAME),
+            width: 'auto',
+            focusAfterHide: true,
+            focusOnShowSelector: SELECTORS.INPUTH5PURL
+        });
+
+        // Set the dialogue content, and then show the dialogue.
+        dialogue.set('bodyContent', this._getDialogueContent())
+            .show();
+    },
+
+    /**
+     * Get the H5P iframe
+     *
+     * @method _resolveH5P
+     * @return {Node} The H5P iframe selected.
+     * @private
+     */
+    _getH5PIframe: function() {
+        var selectednode = this.get('host').getSelectionParentNode();
+        if (!selectednode) {
+            return;
+        }
+        return Y.one(selectednode).one('iframe.h5pcontent');
+    },
+
+
+    /**
+     * Return the dialogue content for the tool, attaching any required
+     * events.
+     *
+     * @method _getDialogueContent
+     * @return {Node} The content to place in the dialogue.
+     * @private
+     */
+    _getDialogueContent: function() {
+        var template = Y.Handlebars.compile(TEMPLATE),
+            content = Y.Node.create(template({
+                elementid: this.get('host').get('elementid'),
+                CSS: CSS,
+                component: COMPONENTNAME
+            }));
+
+        this._form = content;
+
+        if (this._placeholderH5P) {
+            var oldurl = this._placeholderH5P.getAttribute('src');
+            this._form.one(SELECTORS.INPUTH5PURL).setAttribute('value', oldurl);
+        }
+
+        this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setH5P, this);
+
+        return content;
+    },
+
+    /**
+     * Set the h5p in the contenteditable.
+     *
+     * @method _setH5P
+     * @param {EventFacade} e
+     * @private
+     */
+    _setH5P: function(e) {
+        var form = this._form,
+            url = form.one(SELECTORS.INPUTH5PURL).get('value'),
+            h5phtml,
+            host = this.get('host');
+
+        e.preventDefault();
+
+        // Check if there are any issues.
+        if (this._updateWarning()) {
+            return;
+        }
+
+        // Focus on the editor in preparation for inserting the h5p.
+        host.focus();
+
+        // If a H5P placeholder was selected we only update the placeholder.
+        if (this._placeholderH5P) {
+            this._placeholderH5P.setAttribute('src', url);
+
+        } else if (url !== '') {
+
+            host.setSelection(this._currentSelection);
+
+            var template = Y.Handlebars.compile(H5PTEMPLATE);
+            h5phtml = template({
+                url: url,
+                allowfullscreen: 'allowfullscreen',
+                allowmedia: 'geolocation *; microphone *; camera *; midi *; encrypted-media *'
+            });
+
+            this.get('host').insertContentAtFocusPoint(h5phtml);
+
+            this.markUpdated();
+        }
+
+        this.getDialogue({
+            focusAfterHide: null
+        }).hide();
+    },
+
+    /**
+     * Check if this could be a h5p URL.
+     *
+     * @method _updateWarning
+     * @param {String} str
+     * @return {boolean} whether a warning should be displayed.
+     * @private
+     */
+    _validURL: function(str) {
+        var pattern = new RegExp('^(https?:\\/\\/)?' + // Protocol.
+            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // Domain name.
+            '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address.
+            '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'); // Port and path.
+        return !!pattern.test(str);
+    },
+
+    /**
+     * Update the url warning.
+     *
+     * @method _updateWarning
+     * @return {boolean} whether a warning should be displayed.
+     * @private
+     */
+    _updateWarning: function() {
+        var form = this._form,
+            state = true,
+            url = form.one('.' + CSS.INPUTH5PURL).get('value');
+        if (this._validURL(url)) {
+            form.one('.' + CSS.URLWARNING).setStyle('display', 'none');
+            state = false;
+        } else {
+            form.one('.' + CSS.URLWARNING).setStyle('display', 'block');
+            state = true;
+        }
+        return state;
+    }
+}, {
+    ATTRS: {
+        /**
+         * The allowedmethods of adding h5p content.
+         *
+         * @attribute allowedmethods
+         * @type String
+         */
+        allowedmethods: {
+            value: null
+        }
+    }
+});
diff --git a/lib/editor/atto/plugins/h5p/yui/src/button/meta/button.json b/lib/editor/atto/plugins/h5p/yui/src/button/meta/button.json
new file mode 100644 (file)
index 0000000..e1cd317
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "moodle-atto_h5p-button": {
+        "requires": [
+            "moodle-editor_atto-plugin"
+        ]
+    }
+}
index 0c083a6..4fe02e9 100644 (file)
@@ -36,7 +36,7 @@ if ($ADMIN->fulltree) {
 style1 = title, bold, italic
 list = unorderedlist, orderedlist
 links = link
-files = image, media, recordrtc, managefiles
+files = image, media, recordrtc, managefiles, h5p
 style2 = underline, strike, subscript, superscript
 align = align
 indent = indent
index 3596c5e..90b3f46 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019052000;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2019090900;        // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019051100;        // Requires this Moodle version.
 $plugin->component = 'editor_atto';  // Full name of the plugin (used for diagnostics).