MDL-66399 atto_h5p: allow h5p file uploads in atto
authorBas Brands <bas@moodle.com>
Wed, 16 Oct 2019 13:33:26 +0000 (15:33 +0200)
committerBas Brands <bas@moodle.com>
Thu, 31 Oct 2019 09:58:37 +0000 (10:58 +0100)
13 files changed:
lib/editor/atto/plugins/h5p/lang/en/atto_h5p.php
lib/editor/atto/plugins/h5p/lib.php
lib/editor/atto/plugins/h5p/pix/icon-white.png [new file with mode: 0644]
lib/editor/atto/plugins/h5p/pix/icon-white.svg [new file with mode: 0644]
lib/editor/atto/plugins/h5p/pix/icon.svg
lib/editor/atto/plugins/h5p/styles.css
lib/editor/atto/plugins/h5p/tests/behat/h5p.feature
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js
lib/editor/atto/plugins/h5p/yui/src/button/js/button.js
lib/editor/atto/tests/fixtures/ipsums.h5p [new file with mode: 0644]
lib/form/editor.php

index 2343281..382ba5f 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['enterurl'] = 'Enter URL';
+$string['browserepositories'] = 'Browse repositories...';
+$string['copyrightbutton'] = 'Copyright button';
+$string['downloadbutton'] = 'Allow download';
+$string['either'] = 'Either';
+$string['embedbutton'] = 'Embed button';
+$string['enterurl'] = 'URL or Embed code';
+$string['h5p:addembed'] = 'Add embedded H5P';
+$string['h5pfile'] = 'H5P file upload';
+$string['h5poptions'] = 'H5P options';
 $string['h5pproperties'] = 'H5P properties';
+$string['h5purl'] = 'H5P URL';
 $string['invalidh5purl'] = 'Invalid URL';
+$string['instructions'] = 'You can insert H5P content by <strong>either</strong> entering a URL or embed code from an external H5P site <strong>or</strong> by uploading an H5P file.';
+$string['noh5pcontent'] = 'No H5P content added';
 $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
+$string['or'] = 'or';
\ No newline at end of file
index d07ad2a..60b34e7 100644 (file)
@@ -36,14 +36,23 @@ function atto_h5p_params_for_js($elementid, $options, $fpoptions) {
     if (!$context) {
         $context = context_system::instance();
     }
+
     $addembed = has_capability('atto/h5p:addembed', $context);
+    $upload = has_capability('moodle/h5p:deploy', $context);
 
     $allowedmethods = 'none';
-    if ($addembed) {
+    if ($addembed && $upload) {
+        $allowedmethods = 'both';
+    } else if ($addembed) {
         $allowedmethods = 'embed';
+    } else if ($upload) {
+        $allowedmethods = 'upload';
     }
 
-    $params = ['allowedmethods' => $allowedmethods];
+    $params = [
+        'allowedmethods' => $allowedmethods,
+        'storeinrepo' => true
+    ];
     return $params;
 }
 
@@ -54,10 +63,21 @@ function atto_h5p_strings_for_js() {
     global $PAGE;
 
     $strings = array(
-        'saveh5p',
-        'h5pproperties',
+        'browserepositories',
+        'copyrightbutton',
+        'downloadbutton',
+        'instructions',
+        'either',
+        'embedbutton',
         'enterurl',
-        'invalidh5purl'
+        'h5pfile',
+        'h5poptions',
+        'h5pproperties',
+        'h5purl',
+        'invalidh5purl',
+        'noh5pcontent',
+        'or',
+        'pluginname'
     );
 
     $PAGE->requires->strings_for_js($strings, 'atto_h5p');
diff --git a/lib/editor/atto/plugins/h5p/pix/icon-white.png b/lib/editor/atto/plugins/h5p/pix/icon-white.png
new file mode 100644 (file)
index 0000000..d2ac8b2
Binary files /dev/null and b/lib/editor/atto/plugins/h5p/pix/icon-white.png differ
diff --git a/lib/editor/atto/plugins/h5p/pix/icon-white.svg b/lib/editor/atto/plugins/h5p/pix/icon-white.svg
new file mode 100644 (file)
index 0000000..3f3ba11
--- /dev/null
@@ -0,0 +1,18 @@
+<?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">
+<style type="text/css">
+       .st0{fill:#FFFFFF;}
+</style>
+<g>
+       <path class="st0" 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.3
+               c4.4-1.1,8.8-0.9,13.1-0.9c14.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,20
+               c-3.1,1.6-6.5,3.1-10.2,4.1h42.4H259V95h25c18.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.4H259V33h13.2c8.4,0,14.2,1.5,17.2,4.7c3.1,3.2,4.6,6.9,4.6,11.5
+               C294,53.9,292.2,57.6,288.7,60.6z"/>
+       <path class="st0" 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>
index 2aaf327..7856f9e 100644 (file)
@@ -1,7 +1,7 @@
 <?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" preserveAspectRatio="xMinYMid meet">
+        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
index c808561..4fecf84 100644 (file)
@@ -1,17 +1,20 @@
 .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;
+.attoh5pinstructions {
+    max-width: 500px;
 }
-.h5p-embed-placeholder .attoh5poverlay + br {
+.editor_atto_content_wrap .h5p-placeholder + br {
     display: none;
+}
+.editor_atto_content_wrap .h5p-placeholder {
+    color: #6c757d;
+    width: 100%;
+    word-break: break-all;
+    height: 260px;
+    cursor: pointer;
+    background: url([[pix:atto_h5p|icon-white]]) center center / 100px auto no-repeat #6c757d;
+}
+.atto_h5p_button .icon {
+    width: 24px;
 }
\ No newline at end of file
index 1d2c920..467e74e 100644 (file)
@@ -1,4 +1,4 @@
-@editor @editor_atto @atto @atto_h5p @_switch_iframe
+@editor @editor_atto @atto @atto_h5p @_file_upload @_switch_iframe
 Feature: Add h5ps to Atto
   To write rich text - I need to add h5ps.
 
@@ -19,38 +19,82 @@ Feature: Add h5ps to Atto
   @javascript
   Scenario: Insert an embedded h5p
     Given I log in as "admin"
+    And I change window size to "large"
     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 set the field with xpath "//textarea[@data-region='h5purl']" to "https://h5p.org/h5p/embed/576651"
+    And I click on "Insert H5P" "button" in the "Insert H5P" "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
+    Then ".h5p-placeholder" "css_element" should exist
+
+  @javascript
+  Scenario: Insert an h5p file
+    Given I log in as "admin"
+    And I change window size to "large"
+    And I follow "Manage private files..."
+    And I upload "lib/editor/atto/tests/fixtures/ipsums.h5p" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    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 click on "Browse repositories..." "button" in the "Insert H5P" "dialogue"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "ipsums.h5p" "link"
+    And I click on "Select this file" "button"
+    And I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
+    And I wait until the page is ready
+    When I click on "Save and display" "button"
+    Then ".h5p-placeholder" "css_element" should exist
 
   @javascript
   Scenario: Test an invalid url
     Given I log in as "admin"
+    And I change window size to "large"
     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 set the field with xpath "//textarea[@data-region='h5purl']" to "ftp://h5p.org/h5p/embed/576651"
+    When I click on "Insert H5P" "button" in the "Insert H5P" "dialogue"
     And I wait until the page is ready
-    Then I should see "Invalid URL" in the "H5P properties" "dialogue"
+    Then I should see "Invalid URL" in the "Insert H5P" "dialogue"
+
+  @javascript
+  Scenario: No h5p capabilities
+    Given the following "permission overrides" exist:
+    | capability | permission | role | contextlevel | reference |
+    | atto/h5p:addembed | Prohibit | editingteacher | Course | C1 |
+    | moodle/h5p:deploy | Prohibit | editingteacher | Course | C1 |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    When I navigate to "Edit settings" in current page administration
+    Then "Insert H5P" "button" should not exist
 
   @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
+    Given the following "permission overrides" exist:
+    | capability | permission | role | contextlevel | reference |
+    | atto/h5p:addembed | Prohibit | editingteacher | Course | C1 |
     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
+    When I navigate to "Edit settings" in current page administration
+    And I click on "Insert H5P" "button"
+    Then I should not see "URL or Embed code" in the "Insert H5P" "dialogue"
+
+  @javascript
+  Scenario: No upload h5p capabilities
+    Given the following "permission overrides" exist:
+    | capability | permission | role | contextlevel | reference |
+    | moodle/h5p:deploy | Prohibit | editingteacher | Course | C1 |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    When I navigate to "Edit settings" in current page administration
+    And I click on "Insert H5P" "button"
+    Then I should not see "H5P file upload" in the "Insert H5P" "dialogue"
index f954471..61169c4 100644 (file)
Binary files a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-debug.js differ
index 5661975..a31effc 100644 (file)
Binary files a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js differ
index f954471..61169c4 100644 (file)
Binary files a/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js and b/lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button.js differ
index d79e190..dbe30b3 100644 (file)
  */
 
 var CSS = {
+        CONTENTWARNING: 'att_h5p_contentwarning',
+        H5PBROWSER: 'openh5pbrowser',
         INPUTALT: 'atto_h5p_altentry',
-        INPUTSUBMIT: 'atto_h5p_urlentrysubmit',
+        INPUTH5PFILE: 'atto_h5p_file',
         INPUTH5PURL: 'atto_h5p_url',
+        INPUTSUBMIT: 'atto_h5p_urlentrysubmit',
+        OPTION_DOWNLOAD_BUTTON: 'atto_h5p_option_download_button',
+        OPTION_COPYRIGHT_BUTTON: 'atto_h5p_option_copyright_button',
+        OPTION_EMBED_BUTTON: 'atto_h5p_option_embed_button',
         URLWARNING: 'atto_h5p_warning'
     },
     SELECTORS = {
-        INPUTH5PURL: '.' + CSS.INPUTH5PURL
+        CONTENTWARNING: '.' + CSS.CONTENTWARNING,
+        H5PBROWSER: '.' + CSS.H5PBROWSER,
+        INPUTH5PFILE: '.' + CSS.INPUTH5PFILE,
+        INPUTH5PURL: '.' + CSS.INPUTH5PURL,
+        INPUTSUBMIT: '.' + CSS.INPUTSUBMIT,
+        OPTION_DOWNLOAD_BUTTON: '.' + CSS.OPTION_DOWNLOAD_BUTTON,
+        OPTION_COPYRIGHT_BUTTON: '.' + CSS.OPTION_COPYRIGHT_BUTTON,
+        OPTION_EMBED_BUTTON: '.' + CSS.OPTION_EMBED_BUTTON,
+        URLWARNING: '.' + CSS.URLWARNING
     },
 
     COMPONENTNAME = 'atto_h5p',
 
     TEMPLATE = '' +
-            '<form class="atto_form">' +
+            '<form class="atto_form mform" id="{{elementid}}_atto_h5p_form">' +
+                '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.CONTENTWARNING}}">' +
+                    '{{get_string "noh5pcontent" component}}' +
+                '</div>' +
+                '{{#if canUploadAndEmbed}}' +
+                    '<div class="mt-2 attoh5pinstructions">{{{get_string "instructions" component}}}</div>' +
+                    '<div class="my-2"><strong>{{get_string "either" component}}</strong></div>' +
+                '{{/if}}' +
+                '{{#if canEmbed}}' +
                 '<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"/>' +
+                    '<textarea rows="3" data-region="h5purl" class="form-control {{CSS.INPUTH5PURL}}" type="url" ' +
+                    'id="{{elementid}}_{{CSS.INPUTH5PURL}}" />{{embedURL}}</textarea>' +
+                '</div>' +
+                '{{/if}}' +
+                '{{#if canUploadAndEmbed}}' +
+                    '<div class="my-2"><strong>{{get_string "or" component}}</strong></div>' +
+                '{{/if}}' +
+                '{{#if canUpload}}' +
+                '<div class="mb-4">' +
+                    '<label for="{{elementid}}_{{CSS.H5PBROWSER}}">{{get_string "h5pfile" component}}</label>' +
+                    '<div class="input-group input-append w-100">' +
+                        '<input class="form-control {{CSS.INPUTH5PFILE}}" type="url" value="{{fileURL}}" ' +
+                        'id="{{elementid}}_{{CSS.INPUTH5PFILE}}" size="32"/>' +
+                        '<span class="input-group-append">' +
+                            '<button class="btn btn-secondary {{CSS.H5PBROWSER}}" type="button">' +
+                            '{{get_string "browserepositories" component}}</button>' +
+                        '</span>' +
+                    '</div>' +
+                    '<fieldset class="collapsible {{#if collapseOptions}}collapsed{{/if}}" id="{{elementid}}_h5poptions">' +
+                        '<legend class="ftoggler">{{get_string "h5poptions" component}}</legend>' +
+                        '<div class="fcontainer">' +
+                            '<div class="form-check">' +
+                                '<input type="checkbox" {{optionDownloadButton}} ' +
+                                'class="form-check-input {{CSS.OPTION_DOWNLOAD_BUTTON}}"' +
+                                'id="{{elementid}}_h5p-option-allow-download"/>' +
+                                '<label class="form-check-label" for="{{elementid}}_h5p-option-allow-download">' +
+                                '{{get_string "downloadbutton" component}}' +
+                                '</label>' +
+                            '</div>' +
+                            '<div class="form-check">' +
+                                '<input type="checkbox" {{optionEmbedButton}} ' +
+                                'class="form-check-input {{CSS.OPTION_EMBED_BUTTON}}" ' +
+                                    'id="{{elementid}}_h5p-option-embed-button"/>' +
+                                '<label class="form-check-label" for="{{elementid}}_h5p-option-embed-button">' +
+                                '{{get_string "embedbutton" component}}' +
+                                '</label>' +
+                            '</div>' +
+                            '<div class="form-check mb-2">' +
+                                '<input type="checkbox" {{optionCopyrightButton}} ' +
+                                'class="form-check-input {{CSS.OPTION_COPYRIGHT_BUTTON}}" ' +
+                                    'id="{{elementid}}_h5p-option-copyright-button"/>' +
+                                '<label class="form-check-label" for="{{elementid}}_h5p-option-copyright-button">' +
+                                '{{get_string "copyrightbutton" component}}' +
+                                '</label>' +
+                            '</div>' +
+                        '</div>' +
+                    '</fieldset>' +
                 '</div>' +
+                '{{/if}}' +
                 '<div class="text-center">' +
                 '<button class="btn btn-secondary {{CSS.INPUTSUBMIT}}" type="submit">' + '' +
-                    '{{get_string "saveh5p" component}}</button>' +
+                    '{{get_string "pluginname" 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/h5p/js/h5p-resizer.js"' +
-                    'charset="UTF-8"></script>' +
-                '</div>' +
-            '</div>' +
-            '<p><br></p>';
+            '<div class="h5p-placeholder">' +
+                '{{{url}}}' +
+            '</div><div><br></div>';
 
 Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
     /**
@@ -93,31 +153,60 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
     _form: null,
 
     /**
-     * A reference to the currently selected H5P placeholder.
+     * A reference to the currently selected H5P div.
      *
      * @param _form
      * @type Node
      * @private
      */
-    _placeholderH5P: null,
+    _H5PDiv: null,
+
+    /**
+     * Allowed methods of adding H5P.
+     *
+     * @param _allowedmethods
+     * @type String
+     * @private
+     */
+    _allowedmethods: 'none',
 
     initializer: function() {
-        var allowedmethods = this.get('allowedmethods');
-        if (allowedmethods !== 'embed') {
+        this._allowedmethods = this.get('allowedmethods');
+        if (this._allowedmethods === 'none') {
             // Plugin not available here.
             return;
         }
-
         this.addButton({
             icon: 'icon',
             iconComponent: 'atto_h5p',
             callback: this._displayDialogue,
-            tags: '.attoh5poverlay',
+            tags: '.h5p-placeholder',
             tagMatchRequiresAll: false
         });
 
-        this.editor.delegate('dblclick', this._handleDblClick, '.attoh5poverlay', this);
-        this.editor.delegate('click', this._handleClick, '.attoh5poverlay', this);
+        this.editor.on(['keyup', 'cut'], this._clearH5P, this);
+        this.editor.delegate('dblclick', this._handleDblClick, '.h5p-placeholder', this);
+        this.editor.delegate('click', this._handleClick, '.h5p-placeholder', this);
+    },
+
+    /**
+     * Deletes elements with class .h5p-placeholder on backspace and delete.
+     *
+     * @method _clearH5P
+     * @param {EventFacade} e
+     * @private
+     */
+    _clearH5P: function(e) {
+        if (e.keyCode === 8 || e.keyCode === 46) {
+            var parentNodes = this.get('host').getSelectedNodes().get('parentNode');
+            if (parentNodes.hasOwnProperty('_nodes')) {
+                var placeholder = parentNodes.filter('.h5p-placeholder');
+                if (!placeholder.isEmpty()) {
+                    placeholder.remove();
+                }
+            }
+        }
+        e.preventDefault();
     },
 
     /**
@@ -138,9 +227,7 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
      * @private
      */
     _handleClick: function(e) {
-        var h5pplaceholder = e.target;
-
-        var selection = this.get('host').getSelectionFromNode(h5pplaceholder);
+        var selection = this.get('host').getSelectionFromNode(e.target);
         if (this.get('host').getSelection() !== selection) {
             this.get('host').setSelection(selection);
         }
@@ -155,21 +242,22 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
     _displayDialogue: function() {
         // Store the current selection.
         this._currentSelection = this.get('host').getSelection();
-        this._placeholderH5P = this._getH5PIframe();
 
         if (this._currentSelection === false) {
             return;
         }
+
+        this._getH5PDiv();
+
         var dialogue = this.getDialogue({
-            headerContent: M.util.get_string('h5pproperties', COMPONENTNAME),
+            headerContent: M.util.get_string('pluginname', COMPONENTNAME),
             width: 'auto',
-            focusAfterHide: true,
-            focusOnShowSelector: SELECTORS.INPUTH5PURL
+            focusAfterHide: true
         });
-
         // Set the dialogue content, and then show the dialogue.
         dialogue.set('bodyContent', this._getDialogueContent())
             .show();
+        M.form.shortforms({formid: this.get('host').get('elementid') + '_atto_h5p_form'});
     },
 
     /**
@@ -179,12 +267,43 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
      * @return {Node} The H5P iframe selected.
      * @private
      */
-    _getH5PIframe: function() {
-        var selectednode = this.get('host').getSelectionParentNode();
-        if (!selectednode) {
-            return;
+    _getH5PDiv: function() {
+        var selectednodes = this.get('host').getSelectedNodes();
+        var H5PDiv = null;
+        selectednodes.each(function(selNode) {
+            if (selNode.hasClass('h5p-placeholder')) {
+                H5PDiv = selNode;
+            }
+        });
+        this._H5PDiv = H5PDiv;
+    },
+
+    /**
+     * Get the H5P button permissions.
+     *
+     * @return {Object} H5P button permissions.
+     * @private
+     */
+    _getPermissions: function() {
+        var permissions = {
+            'canUpload': false,
+            'canUploadAndEbmed': false,
+            'canEmbed': false
+        };
+
+        if (this.get('host').canShowFilepicker('h5p')) {
+            if (this._allowedmethods === 'both') {
+                permissions.canUploadAndEmbed = true;
+                permissions.canUpload = true;
+            } else if (this._allowedmethods === 'upload') {
+                permissions.canUpload = true;
+            }
         }
-        return Y.one(selectednode).one('iframe.h5pcontent');
+
+        if (this._allowedmethods === 'both' || this._allowedmethods === 'embed') {
+            permissions.canEmbed = true;
+        }
+        return permissions;
     },
 
 
@@ -197,27 +316,130 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
      * @private
      */
     _getDialogueContent: function() {
+
+        var permissions = this._getPermissions();
+
+        var fileURL,
+            embedURL,
+            optionDownloadButton,
+            optionEmbedButton,
+            optionCopyrightButton,
+            collapseOptions = true;
+
+        if (this._H5PDiv) {
+            var H5PURL = this._H5PDiv.get('innerHTML');
+            var fileBaseUrl = M.cfg.wwwroot + '/draftfile.php';
+            if (fileBaseUrl == H5PURL.substring(0, fileBaseUrl.length)) {
+                fileURL = H5PURL.split("?")[0];
+
+                var parameters = H5PURL.split("?")[1];
+                if (parameters) {
+                    if (parameters.match(/export=1/)) {
+                        optionDownloadButton = 'checked';
+                        collapseOptions = false;
+                    }
+
+                    if (parameters.match(/embed=1/)) {
+                        optionEmbedButton = 'checked';
+                        collapseOptions = false;
+                    }
+
+                    if (parameters.match(/copyright=1/)) {
+                        optionCopyrightButton = 'checked';
+                        collapseOptions = false;
+                    }
+                }
+            } else {
+                embedURL = H5PURL;
+            }
+        }
+
         var template = Y.Handlebars.compile(TEMPLATE),
             content = Y.Node.create(template({
                 elementid: this.get('host').get('elementid'),
                 CSS: CSS,
-                component: COMPONENTNAME
+                component: COMPONENTNAME,
+                canUpload: permissions.canUpload,
+                canEmbed: permissions.canEmbed,
+                fileURL: fileURL,
+                embedURL: embedURL,
+                canUploadAndEmbed: permissions.canUploadAndEmbed,
+                collapseOptions: collapseOptions,
+                optionDownloadButton: optionDownloadButton,
+                optionEmbedButton: optionEmbedButton,
+                optionCopyrightButton: optionCopyrightButton
             }));
 
         this._form = content;
 
-        if (this._placeholderH5P) {
-            var oldurl = this._placeholderH5P.getAttribute('src');
-            this._form.one(SELECTORS.INPUTH5PURL).setAttribute('value', oldurl);
+        // Listen to and act on Dialogue content events.
+        this._setEventListeners();
+
+        return content;
+    },
+
+    /**
+     * Update the dialogue after an h5p was selected in the File Picker.
+     *
+     * @method _filepickerCallback
+     * @param {object} params The parameters provided by the filepicker
+     * containing information about the h5p.
+     * @private
+     */
+    _filepickerCallback: function(params) {
+        if (params.url !== '') {
+            var input = this._form.one(SELECTORS.INPUTH5PFILE);
+            input.set('value', params.url);
+            this._form.one(SELECTORS.INPUTH5PURL).set('value', '');
+            this._removeWarnings();
         }
+    },
 
-        this._form.one('.' + CSS.INPUTSUBMIT).on('click', this._setH5P, this);
+    /**
+     * Set event Listeners for Dialogue content actions.
+     *
+     * @method  _setEventListeners
+     * @private
+     */
+    _setEventListeners: function() {
+        var form = this._form;
+        var permissions = this._getPermissions();
 
-        return content;
+        form.one(SELECTORS.INPUTSUBMIT).on('click', this._setH5P, this);
+
+        if (permissions.canUpload) {
+            form.one(SELECTORS.H5PBROWSER).on('click', function() {
+                this.get('host').showFilepicker('h5p', this._filepickerCallback, this);
+            }, this);
+        }
+
+        if (permissions.canUploadAndEmbed) {
+            form.one(SELECTORS.INPUTH5PFILE).on('change', function() {
+                form.one(SELECTORS.INPUTH5PURL).set('value', '');
+                this._removeWarnings();
+            }, this);
+            form.one(SELECTORS.INPUTH5PURL).on('change', function() {
+                form.one(SELECTORS.INPUTH5PFILE).set('value', '');
+                this._removeWarnings();
+            }, this);
+        }
     },
 
     /**
-     * Set the h5p in the contenteditable.
+     * Remove warnings shown in the dialogue.
+     *
+     * @method _removeWarnings
+     * @private
+     */
+    _removeWarnings: function() {
+        var form = this._form;
+        form.one(SELECTORS.URLWARNING).setStyle('display', 'none');
+        form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'none');
+    },
+
+    /**
+     * Update the h5p in the contenteditable.
+
      *
      * @method _setH5P
      * @param {EventFacade} e
@@ -227,7 +449,17 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
         var form = this._form,
             url = form.one(SELECTORS.INPUTH5PURL).get('value'),
             h5phtml,
-            host = this.get('host');
+            host = this.get('host'),
+            h5pfile,
+            permissions = this._getPermissions();
+
+        if (permissions.canEmbed) {
+            url = form.one(SELECTORS.INPUTH5PURL).get('value');
+        }
+
+        if (permissions.canUpload) {
+            h5pfile = form.one(SELECTORS.INPUTH5PFILE).get('value');
+        }
 
         e.preventDefault();
 
@@ -239,19 +471,69 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
         // 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);
+        // If a H5P placeholder was selected we can destroy it now.
+        if (this._H5PDiv) {
+            this._H5PDiv.remove();
+        }
 
-        } else if (url !== '') {
+        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 *'
+            if (this._validEmbed(url)) {
+                var embedtemplate = Y.Handlebars.compile(H5PTEMPLATE);
+                var regex = /<iframe.*?src="(.*?)".*<\/iframe>/;
+                var src = url.match(regex)[1];
+
+                // In case a local H5P embed code is used we need get the url
+                // param form the src and decode it.
+                if (src.startsWith(M.cfg.wwwroot + '/h5p/embed.php')) {
+                    src = decodeURIComponent(src.split("url=")[1]);
+                }
+
+                h5phtml = embedtemplate({
+                    url: src
+                });
+            } else {
+                var urltemplate = Y.Handlebars.compile(H5PTEMPLATE);
+                h5phtml = urltemplate({
+                    url: url
+                });
+            }
+
+            this.get('host').insertContentAtFocusPoint(h5phtml);
+
+            this.markUpdated();
+        } else if (h5pfile !== '') {
+
+            host.setSelection(this._currentSelection);
+
+            var options = {};
+
+            if (form.one(SELECTORS.OPTION_DOWNLOAD_BUTTON).get('checked')) {
+                options['export'] = '1';
+            }
+            if (form.one(SELECTORS.OPTION_EMBED_BUTTON).get('checked')) {
+                options.embed = '1';
+            }
+            if (form.one(SELECTORS.OPTION_COPYRIGHT_BUTTON).get('checked')) {
+                options.copyright = '1';
+            }
+
+            var params = "";
+            for (var opt in options) {
+                if (params === "" && (h5pfile.indexOf("?") === -1)) {
+                    params += "?";
+                } else {
+                    params += "&amp;";
+                }
+                params += opt + "=" + options[opt];
+            }
+
+            var h5ptemplate = Y.Handlebars.compile(H5PTEMPLATE);
+
+            h5phtml = h5ptemplate({
+                url: h5pfile + params
             });
 
             this.get('host').insertContentAtFocusPoint(h5phtml);
@@ -264,12 +546,25 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
         }).hide();
     },
 
+    /**
+     * Check if this could be a h5p embed.
+     *
+     * @method _validEmbed
+     * @param {String} str
+     * @return {boolean} whether this is a iframe tag.
+     * @private
+     */
+    _validEmbed: function(str) {
+        var pattern = new RegExp('^(<iframe).*(<\\/iframe>)'); // Port and path.
+        return !!pattern.test(str);
+    },
+
     /**
      * Check if this could be a h5p URL.
      *
-     * @method _updateWarning
+     * @method _validURL
      * @param {String} str
-     * @return {boolean} whether a warning should be displayed.
+     * @return {boolean} whether this is a valid URL.
      * @private
      */
     _validURL: function(str) {
@@ -290,14 +585,36 @@ Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.Edito
     _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;
+            url,
+            h5pfile,
+            permissions = this._getPermissions();
+
+
+        if (permissions.canEmbed) {
+            url = form.one(SELECTORS.INPUTH5PURL).get('value');
+            if (url !== '') {
+                if (this._validURL(url) || this._validEmbed(url)) {
+                    form.one(SELECTORS.URLWARNING).setStyle('display', 'none');
+                    state = false;
+                } else {
+                    form.one(SELECTORS.URLWARNING).setStyle('display', 'block');
+                    state = true;
+                }
+                return state;
+            }
         }
+
+        if (permissions.canUpload) {
+            h5pfile = form.one(SELECTORS.INPUTH5PFILE).get('value');
+            if (h5pfile !== '') {
+                form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'none');
+                state = false;
+            } else {
+                form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'block');
+                state = true;
+            }
+        }
+
         return state;
     }
 }, {
diff --git a/lib/editor/atto/tests/fixtures/ipsums.h5p b/lib/editor/atto/tests/fixtures/ipsums.h5p
new file mode 100644 (file)
index 0000000..a903fd5
Binary files /dev/null and b/lib/editor/atto/tests/fixtures/ipsums.h5p differ
index bbec709..f0631f9 100644 (file)
@@ -390,10 +390,21 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element implements templatab
             $subtitle_options->env = 'editor';
             $subtitle_options->itemid = $draftitemid;
 
+            // H5P plugin.
+            $args->accepted_types = array('.h5p');
+            $h5poptions = initialise_filepicker($args);
+            $h5poptions->context = $ctx;
+            $h5poptions->client_id = uniqid();
+            $h5poptions->maxbytes  = $this->_options['maxbytes'];
+            $h5poptions->areamaxbytes  = $this->_options['areamaxbytes'];
+            $h5poptions->env = 'editor';
+            $h5poptions->itemid = $draftitemid;
+
             $fpoptions['image'] = $image_options;
             $fpoptions['media'] = $media_options;
             $fpoptions['link'] = $link_options;
             $fpoptions['subtitle'] = $subtitle_options;
+            $fpoptions['h5p'] = $h5poptions;
         }
 
         //If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.