Merge branch 'MDL-56846-master' of git://github.com/damyon/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 15 Nov 2016 00:05:54 +0000 (08:05 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 15 Nov 2016 00:05:54 +0000 (08:05 +0800)
245 files changed:
admin/category.php
admin/environment.xml
admin/index.php
admin/message.php
admin/roles/admins.php
admin/roles/allow.php
admin/roles/assign.php
admin/roles/check.php
admin/roles/classes/define_role_table_advanced.php
admin/roles/classes/define_role_table_basic.php
admin/roles/define.php
admin/roles/module.js
admin/roles/override.php
admin/settings/appearance.php
admin/settings/security.php
admin/tool/behat/tests/behat/nasty_strings.feature
admin/tool/langimport/classes/controller.php
admin/tool/lp/amd/build/menubar.min.js
admin/tool/lp/amd/src/menubar.js
admin/tool/lp/classes/form/framework_autocomplete.php
admin/tool/lp/templates/competency_plan_navigation.mustache
admin/tool/lp/templates/course_competencies_page.mustache
admin/tool/lp/templates/manage_competencies_page.mustache
admin/tool/lp/templates/manage_competency_frameworks_page.mustache
admin/tool/lp/templates/manage_templates_page.mustache
admin/tool/lp/templates/plan_page.mustache
admin/tool/lp/templates/plans_page.mustache
admin/tool/lp/templates/user_competency_course_navigation.mustache
admin/tool/lp/templates/user_evidence_list_page.mustache
admin/tool/lpimportcsv/classes/form/import.php
admin/tool/lpimportcsv/lang/en/tool_lpimportcsv.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/settings.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/monitor/index.php
admin/tool/usertours/amd/build/popper.min.js
admin/tool/usertours/amd/build/tour.min.js
admin/tool/usertours/amd/src/popper.js
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/classes/manager.php
admin/tool/usertours/thirdpartylibs.xml
availability/classes/tree_node.php
availability/condition/completion/tests/behat/conditional_bug.feature
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js
availability/condition/completion/yui/src/form/js/form.js
availability/condition/date/classes/frontend.php
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js
availability/condition/date/yui/src/form/js/form.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-debug.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form.js
availability/condition/grade/yui/src/form/js/form.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-debug.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form.js
availability/condition/group/yui/src/form/js/form.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-debug.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form.js
availability/condition/grouping/yui/src/form/js/form.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-debug.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form.js
availability/condition/profile/yui/src/form/js/form.js
availability/tests/tree_test.php
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js
availability/yui/src/form/js/form.js
backup/util/ui/renderer.php
blog/rsslib.php
calendar/classes/export_form.php
composer.lock
course/externallib.php
course/lib.php
course/upgrade.txt
filter/mediaplugin/tests/filter_test.php
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js
grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js
lang/en/admin.php
lang/en/cache.php
lang/en/calendar.php
lang/en/deprecated.txt
lang/en/message.php
lang/en/moodle.php
lang/en/webservice.php
lib/adminlib.php
lib/amd/build/chart_output_chartjs.min.js
lib/amd/src/chart_output_chartjs.js
lib/classes/component.php
lib/classes/files/curl_security_helper.php [new file with mode: 0644]
lib/classes/files/curl_security_helper_base.php [new file with mode: 0644]
lib/classes/plugininfo/webservice.php
lib/classes/session/manager.php
lib/db/caches.php
lib/deprecatedlib.php
lib/environmentlib.php
lib/externallib.php
lib/filelib.php
lib/form/amd/build/passwordunmask.min.js
lib/form/amd/src/passwordunmask.js
lib/form/select.php
lib/form/templatable_form_element.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputlib.php
lib/outputrenderers.php
lib/phpunit/classes/util.php
lib/setuplib.php
lib/tests/curl_security_helper_test.php [new file with mode: 0644]
lib/tests/filelib_test.php
lib/tests/upgradelib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-debug.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-min.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm.js
lib/yui/src/notification/js/alert.js
lib/yui/src/notification/js/confirm.js
media/player/videojs/settings.php
message/amd/build/message_area_messages.min.js
message/amd/build/preferences_notifications_list_controller.min.js
message/amd/src/message_area_messages.js
message/amd/src/preferences_notifications_list_controller.js
message/classes/api.php
message/lib.php
message/output/popup/classes/api.php
message/output/popup/lib.php
message/tests/api_test.php
mod/assign/amd/build/grading_panel.min.js
mod/assign/amd/build/participant_selector.min.js
mod/assign/amd/src/grading_panel.js
mod/assign/amd/src/participant_selector.js
mod/assign/externallib.php
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/group_annotations.feature
mod/assign/feedback/editpdf/tests/behat/view_previous_annotations.feature
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/locallib.php
mod/assign/styles.css
mod/assign/tests/behat/allow_another_attempt.feature
mod/assign/tests/behat/group_submission.feature
mod/assign/upgrade.txt
mod/chat/gui_ajax/index.php
mod/chat/gui_ajax/module.js
mod/chat/gui_basic/index.php
mod/chat/report.php
mod/data/edit.php
mod/data/export_form.php
mod/data/field.php
mod/data/field/checkbox/field.class.php
mod/data/field/date/field.class.php
mod/data/field/file/field.class.php
mod/data/field/latlong/field.class.php
mod/data/field/menu/field.class.php
mod/data/field/multimenu/field.class.php
mod/data/field/number/field.class.php
mod/data/field/picture/field.class.php
mod/data/field/radiobutton/field.class.php
mod/data/field/text/field.class.php
mod/data/field/textarea/field.class.php
mod/data/field/url/field.class.php
mod/data/lib.php
mod/data/renderer.php
mod/data/styles.css
mod/data/templates.php
mod/data/view.php
mod/feedback/edit_form.php
mod/feedback/show_nonrespondents.php
mod/folder/renderer.php
mod/forum/classes/observer.php
mod/forum/db/events.php
mod/forum/discuss.php
mod/forum/version.php
mod/glossary/export.php
mod/glossary/lib.php
mod/glossary/styles.css
mod/glossary/view.php
mod/lesson/edit.php
mod/lesson/lib.php
mod/lti/templates/cartridge_registration_form.mustache
mod/lti/templates/tool_card.mustache
mod/lti/templates/tool_configure.mustache
mod/lti/templates/tool_list.mustache
mod/lti/templates/tool_proxy_card.mustache
mod/quiz/amd/build/preflightcheck.min.js
mod/quiz/amd/src/preflightcheck.js
mod/quiz/lib.php
mod/quiz/tests/behat/preview.feature [new file with mode: 0644]
mod/quiz/view.php
mod/workshop/classes/portfolio_caller.php
notes/index.php
question/type/essay/question.php
question/type/questionbase.php
theme/boost/classes/output/core_renderer.php
theme/boost/lang/en/theme_boost.php
theme/boost/layout/columns1.php
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/backup-restore.scss
theme/boost/scss/moodle/buttons.scss
theme/boost/scss/moodle/chat.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/filemanager.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/user.scss
theme/boost/templates/columns1.mustache
theme/boost/templates/core/block.mustache
theme/boost/templates/core/filemanager_loginform.mustache
theme/boost/templates/core/navbar.mustache
theme/boost/templates/core/single_button.mustache
theme/boost/templates/core/url_select.mustache
theme/boost/templates/core_form/element-autocomplete-inline.mustache
theme/boost/templates/core_form/element-select-inline.mustache
theme/boost/templates/core_form/element-select.mustache
theme/boost/templates/core_form/element-selectwithlink.mustache
theme/boost/templates/core_form/element-tags-inline.mustache
theme/boost/templates/core_form/element-tags.mustache
theme/boost/tests/behat/behat_theme_boost_behat_admin.php
theme/bootstrapbase/less/moodle/admin.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/style/moodle.css
theme/upgrade.txt
user/externallib.php
user/selector/lib.php
user/selector/module.js
user/tests/externallib_test.php
version.php
webservice/externallib.php
webservice/tests/externallib_test.php

index 7847b2d..cb83e0d 100644 (file)
@@ -120,7 +120,7 @@ foreach ($settingspage->children as $childpage) {
 }
 if ($savebutton) {
     $outputhtml .= html_writer::start_tag('div', array('class' => 'form-buttons'));
-    $outputhtml .= html_writer::empty_tag('input', array('class' => 'form-submit', 'type' => 'submit', 'value' => get_string('savechanges','admin')));
+    $outputhtml .= html_writer::empty_tag('input', array('class' => 'btn btn-primary form-submit', 'type' => 'submit', 'value' => get_string('savechanges','admin')));
     $outputhtml .= html_writer::end_tag('div');
 }
 
index 5775d1a..8c35fb6 100644 (file)
           <ON_CHECK message="unsupporteddbtablerowformat" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
   <MOODLE version="2.8" requires="2.2">
           <ON_CHECK message="unsupporteddbtablerowformat" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
   <MOODLE version="3.0" requires="2.2">
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="5.4.4" level="required">
+      <RESTRICT function="restrict_php_version_71" message="unsupportedphpversion71" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
           <ON_CHECK message="unsupporteddbtablerowformat" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
   <MOODLE version="3.1" requires="2.7">
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="5.4.4" level="required">
+      <RESTRICT function="restrict_php_version_71" message="unsupportedphpversion71" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
           <ON_CHECK message="unoconvwarning" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
   <MOODLE version="3.2" requires="2.7">
           <ON_CHECK message="tlswarning" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
 </COMPATIBILITY_MATRIX>
index 6f87285..30ba5cb 100644 (file)
@@ -315,6 +315,11 @@ if (!$cache and $version > $CFG->version) {  // upgrade
         $testsite = 'behat';
     }
 
+    if (isset($CFG->themerev)) {
+        // Store the themerev to restore after purging caches.
+        $themerev = $CFG->themerev;
+    }
+
     // We purge all of MUC's caches here.
     // Caches are disabled for upgrade by CACHE_DISABLE_ALL so we must set the first arg to true.
     // This ensures a real config object is loaded and the stores will be purged.
@@ -324,6 +329,11 @@ if (!$cache and $version > $CFG->version) {  // upgrade
     // We then purge the regular caches.
     purge_all_caches();
 
+    if (isset($themerev)) {
+        // Restore the themerev
+        set_config('themerev', $themerev);
+    }
+
     $output = $PAGE->get_renderer('core', 'admin');
 
     if (upgrade_stale_php_files_present()) {
index 52cb560..6924e61 100644 (file)
@@ -41,7 +41,7 @@ if (!empty($disable) && confirm_sesskey()) {
     if (!$processor = $DB->get_record('message_processors', array('id'=>$disable))) {
         print_error('outputdoesnotexist', 'message');
     }
-    $DB->set_field('message_processors', 'enabled', '0', array('id'=>$processor->id));      // Disable output
+    \core_message\api::update_processor_status($processor, 0);     // Disable output.
     core_plugin_manager::reset_caches();
 }
 
@@ -49,7 +49,7 @@ if (!empty($enable) && confirm_sesskey()) {
     if (!$processor = $DB->get_record('message_processors', array('id'=>$enable))) {
         print_error('outputdoesnotexist', 'message');
     }
-    $DB->set_field('message_processors', 'enabled', '1', array('id'=>$processor->id));      // Enable output
+    \core_message\api::update_processor_status($processor, 1);      // Enable output.
     core_plugin_manager::reset_caches();
 }
 
index 57833b6..8245949 100644 (file)
@@ -133,9 +133,12 @@ echo $OUTPUT->header();
           </td>
       <td id="buttonscell">
         <p class="arrow_button">
-            <input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
-            <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
-            <input name="main" id="main" type="submit" value="<?php echo get_string('mainadminset', 'core_role'); ?>" title="<?php print_string('mainadminset', 'core_role'); ?>" />
+            <input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>"
+                   title="<?php print_string('add'); ?>" class="btn btn-secondary"/><br />
+            <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>"
+                   title="<?php print_string('remove'); ?>" class="btn btn-secondary"/><br />
+            <input name="main" id="main" type="submit" value="<?php echo get_string('mainadminset', 'core_role'); ?>"
+                   title="<?php print_string('mainadminset', 'core_role'); ?>" class="btn btn-secondary"/>
         </p>
       </td>
       <td id="potentialcell">
index 383455e..916e6c2 100644 (file)
@@ -80,7 +80,8 @@ echo $OUTPUT->box($controller->get_intro_text());
 echo '<form action="' . $baseurl . '" method="post">';
 echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
 echo html_writer::table($table);
-echo '<div class="buttons"><input type="submit" name="submit" value="'.get_string('savechanges').'"/>';
+echo '<div class="buttons">';
+echo '<input type="submit" class="btn btn-primary" name="submit" value="' . get_string('savechanges') . '"/>';
 echo '</div></form>';
 
 echo $OUTPUT->footer();
index 3d723b8..f535866 100644 (file)
@@ -201,11 +201,13 @@ if ($roleid) {
       </td>
       <td id="buttonscell">
           <div id="addcontrols">
-              <input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
+              <input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>"
+                     title="<?php print_string('add'); ?>" class="btn btn-secondary"/><br />
           </div>
 
           <div id="removecontrols">
-              <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
+              <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>"
+                     title="<?php print_string('remove'); ?>" class="btn btn-secondary"/>
           </div>
       </td>
       <td id="potentialcell">
index 6649aaf..0f40308 100644 (file)
@@ -172,7 +172,8 @@ echo $OUTPUT->heading('<label for="reportuser">' . $selectheading . '</label>',
 $userselector->display();
 
 // Submit button and the end of the form.
-echo '<p id="chooseusersubmit"><input type="submit" value="' . get_string('showthisuserspermissions', 'core_role') . '" /></p>';
+echo '<p id="chooseusersubmit"><input type="submit" value="' . get_string('showthisuserspermissions', 'core_role') . '" ' .
+     'class="btn btn-primary"/></p>';
 echo '</form>';
 echo $OUTPUT->box_end();
 
index eb46c76..bcf6a49 100644 (file)
@@ -471,15 +471,17 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     }
 
     protected function get_name_field($id) {
-        return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '" />';
+        return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->name) . '"' .
+                ' class="form-control"/>';
     }
 
     protected function get_shortname_field($id) {
-        return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->shortname) . '" />';
+        return '<input type="text" id="' . $id . '" name="' . $id . '" maxlength="254" value="' . s($this->role->shortname) . '"' .
+                ' class="form-control"/>';
     }
 
     protected function get_description_field($id) {
-        return '<textarea class="form-textarea" id="'. s($id) .'" name="description" rows="10" cols="50">' .
+        return '<textarea class="form-textarea form-control" id="'. s($id) .'" name="description" rows="10" cols="50">' .
             htmlspecialchars($this->role->description) .
             '</textarea>';
     }
@@ -490,7 +492,8 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         foreach (get_role_archetypes() as $type) {
             $options[$type] = get_string('archetype'.$type, 'role');
         }
-        return html_writer::select($options, 'archetype', $this->role->archetype, false);
+        return html_writer::select($options, 'archetype', $this->role->archetype, false,
+            array('class' => 'custom-select'));
     }
 
     protected function get_assignable_levels_control() {
@@ -563,7 +566,8 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         if ($this->roleid == 0) {
             $options[-1] = get_string('thisnewrole', 'core_role');
         }
-        return html_writer::select($options, 'allow'.$type.'[]', $selected, false, array('multiple'=>'multiple', 'size'=>10));
+        return html_writer::select($options, 'allow'.$type.'[]', $selected, false, array('multiple' => 'multiple',
+            'size' => 10, 'class' => 'form-control'));
     }
 
     /**
@@ -575,11 +579,19 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         return '';
     }
 
-    protected function print_field($name, $caption, $field) {
+    /**
+     * Print labels, fields and help icon on role administration page.
+     *
+     * @param string $name The field name.
+     * @param string $caption The field caption.
+     * @param string $field The field type.
+     * @param null|string $helpicon The help icon content.
+     */
+    protected function print_field($name, $caption, $field, $helpicon = null) {
         global $OUTPUT;
         // Attempt to generate HTML like formslib.
-        echo '<div class="fitem">';
-        echo '<div class="fitemtitle">';
+        echo '<div class="fitem row form-group">';
+        echo '<div class="fitemtitle col-md-3">';
         if ($name) {
             echo '<label for="' . $name . '">';
         }
@@ -587,13 +599,16 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         if ($name) {
             echo "</label>\n";
         }
+        if ($helpicon) {
+            echo '<span class="pull-xs-right text-nowrap">'.$helpicon.'</span>';
+        }
         echo '</div>';
         if (isset($this->errors[$name])) {
             $extraclass = ' error';
         } else {
             $extraclass = '';
         }
-        echo '<div class="felement' . $extraclass . '">';
+        echo '<div class="felement col-md-9 form-inline' . $extraclass . '">';
         echo $field;
         if (isset($this->errors[$name])) {
             echo $OUTPUT->error_text($this->errors[$name]);
@@ -605,7 +620,8 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     protected function print_show_hide_advanced_button() {
         echo '<p class="definenotice">' . get_string('highlightedcellsshowdefault', 'core_role') . ' </p>';
         echo '<div class="advancedbutton">';
-        echo '<input type="submit" name="toggleadvanced" value="' . get_string('hideadvanced', 'form') . '" />';
+        echo '<input type="submit" class="btn btn-secondary" name="toggleadvanced" value="' .
+            get_string('hideadvanced', 'form') . '" />';
         echo '</div>';
     }
 
@@ -613,11 +629,14 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         global $OUTPUT;
         // Extra fields at the top of the page.
         echo '<div class="topfields clearfix">';
-        $this->print_field('shortname', get_string('roleshortname', 'core_role').'&nbsp;'.$OUTPUT->help_icon('roleshortname', 'core_role'), $this->get_shortname_field('shortname'));
-        $this->print_field('name', get_string('customrolename', 'core_role').'&nbsp;'.$OUTPUT->help_icon('customrolename', 'core_role'), $this->get_name_field('name'));
-        $this->print_field('edit-description', get_string('customroledescription', 'core_role').'&nbsp;'.$OUTPUT->help_icon('customroledescription', 'core_role'),
-            $this->get_description_field('description'));
-        $this->print_field('menuarchetype', get_string('archetype', 'core_role').'&nbsp;'.$OUTPUT->help_icon('archetype', 'core_role'), $this->get_archetype_field('archetype'));
+        $this->print_field('shortname', get_string('roleshortname', 'core_role'),
+            $this->get_shortname_field('shortname'), $OUTPUT->help_icon('roleshortname', 'core_role'));
+        $this->print_field('name', get_string('customrolename', 'core_role'), $this->get_name_field('name'),
+            $OUTPUT->help_icon('customrolename', 'core_role'));
+        $this->print_field('edit-description', get_string('customroledescription', 'core_role'),
+            $this->get_description_field('description'), $OUTPUT->help_icon('customroledescription', 'core_role'));
+        $this->print_field('menuarchetype', get_string('archetype', 'core_role'), $this->get_archetype_field('archetype'),
+            $OUTPUT->help_icon('archetype', 'core_role'));
         $this->print_field('', get_string('maybeassignedin', 'core_role'), $this->get_assignable_levels_control());
         $this->print_field('menuallowassign', get_string('allowassign', 'core_role'), $this->get_allow_role_control('assign'));
         $this->print_field('menuallowoverride', get_string('allowoverride', 'core_role'), $this->get_allow_role_control('override'));
index 0c3d692..3349377 100644 (file)
@@ -37,7 +37,8 @@ class core_role_define_role_table_basic extends core_role_define_role_table_adva
 
     protected function print_show_hide_advanced_button() {
         echo '<div class="advancedbutton">';
-        echo '<input type="submit" name="toggleadvanced" value="' . get_string('showadvanced', 'form') . '" />';
+        echo '<input type="submit" class="btn btn-secondary" name="toggleadvanced"
+            value="' . get_string('showadvanced', 'form') . '" />';
         echo '</div>';
     }
 
index 093c693..d670bae 100644 (file)
@@ -252,7 +252,7 @@ if ($action === 'view') {
     echo '<div class="mform">';
 } else {
     ?>
-<form id="rolesform" class="mform" action="<?php p($baseurl->out(false)); ?>" method="post"><div>
+<form id="rolesform" class="mform fcontainer" action="<?php p($baseurl->out(false)); ?>" method="post"><div>
 <input type="hidden" name="sesskey" value="<?php p(sesskey()) ?>" />
 <input type="hidden" name="return" value="<?php p($return); ?>" />
 <input type="hidden" name="resettype" value="none" />
index 9c331f1..243fd67 100644 (file)
@@ -45,17 +45,20 @@ M.core_role.init_cap_table_filter = function(Y, tableid, contextid) {
             this.table = Y.one('#'+this.tableid);
 
             // Create a div to hold the search UI.
-            this.div = Y.Node.create('<div class="capabilitysearchui"></div>').setStyles({
+            this.div = Y.Node.create('<div class="capabilitysearchui form-inline"></div>').setStyles({
                 width : this.table.get('offsetWidth'),
                 marginLeft : 'auto',
                 marginRight : 'auto'
             });
             // Create the capability search input.
-            this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+Y.Escape.html(filtervalue)+'" />');
+            this.input = Y.Node.create('<input class="form-control m-x-1" type="text"' +
+                ' id="'+this.table.get('id')+'capabilitysearch" value="'+Y.Escape.html(filtervalue)+'" />');
             // Create a label for the search input.
-            this.label = Y.Node.create('<label for="'+this.input.get('id')+'">'+M.util.get_string('filter', 'moodle')+' </label>');
+            this.label = Y.Node.create('<label for="' + this.input.get('id') + '">' +
+                M.util.get_string('filter', 'moodle') + ' </label>');
             // Create a clear button to clear the input.
-            this.button = Y.Node.create('<input type="button" value="'+M.util.get_string('clear', 'moodle')+'" />').set('disabled', filtervalue=='');
+            this.button = Y.Node.create('<input type="button" class="btn btn-primary"' +
+                ' value="'+M.util.get_string('clear', 'moodle')+'" />').set('disabled', filtervalue=='');
 
             // Tie it all together
             this.div.append(this.label).append(this.input).append(this.button);
index 1583fab..878c820 100644 (file)
@@ -177,8 +177,10 @@ if (!empty($capabilities)) {
     }
 
     echo html_writer::start_tag('div', array('class'=>'submit_buttons'));
-    echo html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'savechanges', 'value'=>get_string('savechanges')));
-    echo html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'cancel', 'value'=>get_string('cancel')));
+    $attrs = array('type'=>'submit', 'name'=>'savechanges', 'value'=>get_string('savechanges'), 'class'=>'btn btn-primary');
+    echo html_writer::empty_tag('input', $attrs);
+    $attrs = array('type'=>'submit', 'name'=>'cancel', 'value'=>get_string('cancel'), 'class' => 'btn btn-secondary');
+    echo html_writer::empty_tag('input', $attrs);
     echo html_writer::end_tag('div');
     echo html_writer::end_tag('div');
     echo html_writer::end_tag('form');
index 398c777..5a20af8 100644 (file)
@@ -181,7 +181,7 @@ preferences,moodle|/user/preferences.php|preferences',
     $temp->add(new admin_setting_configselect('navsortmycoursessort', new lang_string('navsortmycoursessort', 'admin'), new lang_string('navsortmycoursessort_help', 'admin'), 'sortorder', $sortoptions));
     $temp->add(new admin_setting_configtext('navcourselimit',new lang_string('navcourselimit','admin'),new lang_string('confignavcourselimit', 'admin'),20,PARAM_INT));
     $temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', new lang_string('usesitenameforsitepages', 'admin'), new lang_string('configusesitenameforsitepages', 'admin'), 0));
-    $temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('linkcoursesections', new lang_string('linkcoursesections', 'admin'), new lang_string('linkcoursesections_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('navshowfrontpagemods', new lang_string('navshowfrontpagemods', 'admin'), new lang_string('navshowfrontpagemods_help', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('navadduserpostslinks', new lang_string('navadduserpostslinks', 'admin'), new lang_string('navadduserpostslinks_help', 'admin'), 1));
@@ -236,8 +236,11 @@ preferences,moodle|/user/preferences.php|preferences',
     $temp->add(new admin_setting_configcheckbox('modchooserdefault', new lang_string('modchooserdefault', 'admin'), new lang_string('configmodchooserdefault', 'admin'), 1));
     $ADMIN->add('appearance', $temp);
 
-    // link to tag management interface
-    $ADMIN->add('appearance', new admin_externalpage('managetags', new lang_string('managetags', 'tag'), $CFG->wwwroot.'/tag/manage.php', 'moodle/tag:manage'));
+    // Link to tag management interface.
+    $url = new moodle_url('/tag/manage.php');
+    $hidden = empty($CFG->usetags);
+    $page = new admin_externalpage('managetags', new lang_string('managetags', 'tag'), $url, 'moodle/tag:manage', $hidden);
+    $ADMIN->add('appearance', $page);
 
     $temp = new admin_settingpage('additionalhtml', new lang_string('additionalhtml', 'admin'));
     $temp->add(new admin_setting_heading('additionalhtml_heading', new lang_string('additionalhtml_heading', 'admin'), new lang_string('additionalhtml_desc', 'admin')));
index c892566..1d89d42 100644 (file)
@@ -118,8 +118,15 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('cookiehttponly', new lang_string('cookiehttponly', 'admin'), new lang_string('configcookiehttponly', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowframembedding', new lang_string('allowframembedding', 'admin'), new lang_string('allowframembedding_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('loginpasswordautocomplete', new lang_string('loginpasswordautocomplete', 'admin'), new lang_string('loginpasswordautocomplete_help', 'admin'), 0));
-    $ADMIN->add('security', $temp);
 
+    // Settings elements used by the \core\files\curl_security_helper class.
+    $temp->add(new admin_setting_configmixedhostiplist('curlsecurityblockedhosts',
+               new lang_string('curlsecurityblockedhosts', 'admin'),
+               new lang_string('curlsecurityblockedhostssyntax', 'admin'), ""));
+    $temp->add(new admin_setting_configportlist('curlsecurityallowedport',
+               new lang_string('curlsecurityallowedport', 'admin'),
+               new lang_string('curlsecurityallowedportsyntax', 'admin'), ""));
+    $ADMIN->add('security', $temp);
 
     // "notifications" settingpage
     $temp = new admin_settingpage('notifications', new lang_string('notifications', 'admin'));
index 1c54765..6981227 100644 (file)
@@ -31,9 +31,8 @@ Feature: Transform steps arguments
     And I press "Update profile"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should not see "NASTYSTRING"
-    # BEHAT Transformation regression - See MDL-56397
-    #And the field "Surname" matches value "$NASTYSTRING1"
-    #And the field "City/town" matches value "$NASTYSTRING3"
+    And the field "Surname" matches value "$NASTYSTRING1"
+    And the field "City/town" matches value "$NASTYSTRING3"
 
   Scenario: Use double quotes
     When I set the following fields to these values:
@@ -57,5 +56,4 @@ Feature: Transform steps arguments
     And I should see "My Firstname"
     And I should see "My Surname"
     And the field "First name" matches value "My Firstname $NASTYSTRING1"
-    # BEHAT Transformation regression - See MDL-56397
-    #And the field "Surname" matches value "My Surname $NASTYSTRING2"
+    And the field "Surname" matches value "My Surname $NASTYSTRING2"
index bf43258..3ea368c 100644 (file)
@@ -184,6 +184,8 @@ class controller {
 
         if ($updated) {
             $this->info[] = get_string('langupdatecomplete', 'tool_langimport');
+            // The strings have been changed so we need to purge their cache to ensure users see the changes.
+            get_string_manager()->reset_caches();
         } else {
             $this->info[] = get_string('nolangupdateneeded', 'tool_langimport');
         }
index 6d50e26..627c7b7 100644 (file)
Binary files a/admin/tool/lp/amd/build/menubar.min.js and b/admin/tool/lp/amd/build/menubar.min.js differ
index 76595fe..4673e3d 100644 (file)
@@ -794,6 +794,7 @@ define(['jquery'], function($) {
         this.allItems.addClass('tool-lp-menu-item');
         this.rootMenus.addClass('tool-lp-root-menu');
         this.subMenus.addClass('tool-lp-sub-menu');
+        this.subMenuItems.addClass('dropdown-item');
     };
 
     return /** @alias module:tool_lp/menubar */ {
index 6107047..9505332 100644 (file)
@@ -91,7 +91,7 @@ class framework_autocomplete extends MoodleQuickForm_autocomplete {
         $ids = array();
 
         foreach ($values as $onevalue) {
-            if ((!$this->optionExists($onevalue)) &&
+            if (!empty($onevalue) && (!$this->optionExists($onevalue)) &&
                     ($onevalue !== '_qf__force_multiselect_submission')) {
                 array_push($ids, $onevalue);
             }
index 21bd7af..d658ea3 100644 (file)
@@ -36,7 +36,7 @@
 {{#hascompetencies}}
 <span>
 <label for="competency-nav-{{uniqid}}" class="accesshide">{{#str}}jumptocompetency, tool_lp{{/str}}</label>
-<select id="competency-nav-{{uniqid}}">
+<select class="custom-select" id="competency-nav-{{uniqid}}">
 {{#competencies}}
 <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} {{idnumber}}</option>
 {{/competencies}}
index 39b061e..eaa0f43 100644 (file)
@@ -85,7 +85,7 @@
         <div data-region="coursecompetency-ruleoutcome">
             <label>
                 {{#str}}uponcoursecompletion, tool_lp{{/str}}
-                <select data-field="ruleoutcome" data-id="{{coursecompetency.id}}">
+                <select data-field="ruleoutcome" data-id="{{coursecompetency.id}}" class="custom-select">
                   {{#ruleoutcomeoptions}}
                      <option value="{{value}}" {{#selected}}selected{{/selected}}>{{text}}</option>
                   {{/ruleoutcomeoptions}}
index 8349d31..bf43b5d 100644 (file)
@@ -63,7 +63,7 @@
                             <ul title="{{#str}}edit{{/str}}" class="competencyactionsmenu">
                                 <li>
                                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
-                                    <ul class="dropdown-menu">
+                                    <ul class="dropdown dropdown-menu">
                                     <li class="dropdown-item">
                                         <a href="#" data-action="edit">
                                             {{#pix}}t/edit{{/pix}} {{#str}}edit{{/str}}
index f3d6a3e..2fcac12 100644 (file)
@@ -60,7 +60,7 @@
             <ul title="{{#str}}edit{{/str}}" class="competencyframeworkactions">
                 <li>
                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
-                    <ul class="dropdown-menu">
+                    <ul class="dropdown dropdown-menu">
                         <li class="dropdown-item">
                             <a href="{{pluginbaseurl}}/editcompetencyframework.php?id={{id}}&amp;pagecontextid={{pagecontextid}}">
                                 {{#pix}}t/edit{{/pix}} {{#str}}edit{{/str}}
index f0af972..4a267f6 100644 (file)
@@ -62,7 +62,7 @@
             <ul class="templateactions">
                 <li>
                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
-                    <ul class="dropdown-menu">
+                    <ul class="dropdown dropdown-menu">
                         <li class="dropdown-item">
                             <a href="{{pluginbaseurl}}/edittemplate.php?id={{id}}&amp;pagecontextid={{pagecontextid}}&amp;return=templates">
                                 {{#pix}}t/edit{{/pix}} {{#str}}edit{{/str}}
index d927eae..96f13f0 100644 (file)
                             <ul title="{{#str}}edit{{/str}}" class="user-competency-actions">
                                 <li>
                                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
-                                    <ul class="dropdown-menu">
+                                    <ul class="dropdown dropdown-menu">
                                         {{#usercompetency.isrequestreviewallowed}}
                                             <li class="dropdown-item">
                                                 <a href="#" data-action="request-review">{{#pix}}t/edit, core{{/pix}} {{#str}}requestreview, tool_lp{{/str}}</a>
index 84a4b66..8a3af99 100644 (file)
@@ -70,7 +70,7 @@
                 <ul title="{{#str}}edit{{/str}}" class="planactions">
                 <li>
                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
-                    <ul class="dropdown-menu">
+                    <ul class="dropdown dropdown-menu">
                     <li class="{{^canbeedited}} disabled {{/canbeedited}} dropdown-item">
                         <a href="{{#canbeedited}}
                                     {{pluginbaseurl}}/editplan.php?id={{id}}&amp;userid={{userid}}&amp;return=plans
index 533d01f..31e51ca 100644 (file)
@@ -41,7 +41,7 @@
 {{#hasusers}}
 <span>
 <label for="user-nav-{{uniqid}}" class="accesshide">{{#str}}jumptouser, tool_lp{{/str}}</label>
-<select id="user-nav-{{uniqid}}">
+<select id="user-nav-{{uniqid}}" class="custom-select">
 {{#users}}
 <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{fullname}}</option>
 {{/users}}
@@ -52,7 +52,7 @@
 {{#hascompetencies}}
 <span>
 <label for="competency-nav-{{uniqid}}" class="accesshide">{{#str}}jumptocompetency, tool_lp{{/str}}</label>
-<select id="competency-nav-{{uniqid}}">
+<select id="competency-nav-{{uniqid}}" class="custom-select">
 {{#competencies}}
 <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} {{idnumber}}</option>
 {{/competencies}}
index d24f138..16ae1b7 100644 (file)
@@ -89,7 +89,7 @@
                 <ul title="{{#str}}edit{{/str}}" class="user-evidence-actions">
                 <li>
                     <a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
-                    <ul class="dropdown-menu">
+                    <ul class="dropdown dropdown-menu">
                         <li class="dropdown-item">
                             <a href="{{pluginbaseurl}}/user_evidence_edit.php?id={{id}}&amp;userid={{userid}}&amp;return=list">
                                 {{#pix}}t/edit{{/pix}} {{#str}}editthisuserevidence, tool_lp{{/str}}
index d2c8071..d0d8d11 100644 (file)
@@ -52,6 +52,7 @@ class import extends moodleform {
         $mform = $this->_form;
         $element = $mform->createElement('filepicker', 'importfile', get_string('importfile', 'tool_lpimportcsv'));
         $mform->addElement($element);
+        $mform->addHelpButton('importfile', 'importfile', 'tool_lpimportcsv');
         $mform->addRule('importfile', null, 'required');
         $mform->addElement('hidden', 'confirm', 0);
         $mform->setType('confirm', PARAM_BOOL);
@@ -65,12 +66,10 @@ class import extends moodleform {
         } else {
             $mform->setDefault('delimiter_name', 'comma');
         }
-        $mform->addHelpButton('delimiter_name', 'csvdelimiter', 'tool_lpimportcsv');
 
         $choices = core_text::get_encodings();
         $mform->addElement('select', 'encoding', get_string('encoding', 'tool_lpimportcsv'), $choices);
         $mform->setDefault('encoding', 'UTF-8');
-        $mform->addHelpButton('encoding', 'encoding', 'tool_lpimportcsv');
 
         $this->add_action_buttons(false, get_string('import', 'tool_lpimportcsv'));
     }
index 9064eb2..8c10b04 100644 (file)
@@ -27,16 +27,16 @@ $string['competencyscaledescription'] = 'Competency scale created by import';
 $string['confirmcolumnmappings'] = 'Confirm the column mappings';
 $string['confirm'] = 'Confirm';
 $string['csvdelimiter'] = 'CSV delimiter';
-$string['csvdelimiter_help'] = 'The CSV delimiter is normally a comma.';
 $string['description'] = 'Description';
 $string['descriptionformat'] = 'Description format';
 $string['encoding'] = 'Encoding';
-$string['encoding_help'] = 'The CSV file encoding is usually UTF-8.';
 $string['export'] = 'Export';
 $string['exportid'] = 'Exported ID (optional)';
 $string['exportnavlink'] = 'Export competency framework';
 $string['idnumber'] = 'ID number';
 $string['importfile'] = 'CSV framework description file';
+$string['importfile_help'] = 'A competency framework may be imported via text file. The format of the file can be determined by creating a new competency framework on the site and then exporting it.';
+$string['importfile_link'] = 'admin/tool/lpimportcsv';
 $string['import'] = 'Import';
 $string['invalidimportfile'] = 'File format is invalid.';
 $string['isframework'] = 'Is framework';
index 3094ebd..1b3cdb6 100644 (file)
@@ -246,15 +246,9 @@ class external extends external_api {
         // We must toletare these two exceptions: forcepasswordchangenotice and usernotfullysetup.
         try {
             self::validate_context($context);
-        } catch (Exception $e) {
-            if ($e instanceof moodle_exception) {
-                if (($e->errorcode != 'usernotfullysetup') and
-                    ($e->errorcode != 'forcepasswordchangenotice')) {
-
-                    // In case we receive a different exception, throw it.
-                    throw $e;
-                }
-            } else {
+        } catch (moodle_exception $e) {
+            if ($e->errorcode != 'usernotfullysetup' && $e->errorcode != 'forcepasswordchangenotice') {
+                // In case we receive a different exception, throw it.
                 throw $e;
             }
         }
index 441a19a..c5862e7 100644 (file)
@@ -25,6 +25,7 @@
 $string['autologinkeygenerationlockout'] = 'Auto-login key generation is locked out, too much requests in an hour.';
 $string['autologinnotallowedtoadmins'] = 'Auto-login is not allowed to site admins';
 $string['clickheretolaunchtheapp'] = 'Click here if the app does not open automatically.';
+$string['configmobilecssurl'] = 'A CSS file to customise your mobile app interface.';
 $string['enablesmartappbanners'] = 'Enable Smart App Banners';
 $string['enablesmartappbanners_desc'] = 'This will display a banner promoting the Moodle Mobile app when visiting the site in Mobile Safari.';
 $string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here; otherwise leave the field empty.';
@@ -36,6 +37,11 @@ $string['iosappid_desc'] = 'This setting may be left as default unless you have
 $string['loginintheapp'] = 'Via the app';
 $string['logininthebrowser'] = 'Via a browser window (for SSO plugins)';
 $string['loginintheembeddedbrowser'] = 'Via an embedded browser (for SSO plugins)';
+$string['mobileapp'] = 'Mobile app';
+$string['mobileappearance'] = 'Mobile appearance';
+$string['mobileauthentication'] = 'Mobile authentication';
+$string['mobilecssurl'] = 'CSS';
+$string['mobilesettings'] = 'Mobile settings';
 $string['pluginname'] = 'Moodle Mobile tools';
 $string['smartappbanners'] = 'Smart App Banners (iOS only)';
 $string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
index 496557c..9cc8d90 100644 (file)
@@ -28,7 +28,9 @@ defined('MOODLE_INTERNAL') || die();
 
 if ($hassiteconfig) {
 
-    $temp = new admin_settingpage('mobile', new lang_string('mobile', 'admin'), 'moodle/site:config', false);
+    $ADMIN->add('root', new admin_category('mobileapp', new lang_string('mobileapp', 'tool_mobile')), 'development');
+
+    $temp = new admin_settingpage('mobilesettings', new lang_string('mobilesettings', 'tool_mobile'), 'moodle/site:config', false);
 
     // We should wait to the installation to finish since we depend on some configuration values that are set once
     // the admin user profile is configured.
@@ -41,32 +43,43 @@ if ($hassiteconfig) {
                 new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default));
     }
 
-    $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'admin'),
-                new lang_string('configmobilecssurl', 'admin'), '', PARAM_URL));
+    $ADMIN->add('mobileapp', $temp);
+
+    // Show only mobile settings if the mobile service is enabled.
+    if (!empty($CFG->enablemobilewebservice)) {
+        // Type of login.
+        $temp = new admin_settingpage('mobileauthentication', new lang_string('mobileauthentication', 'tool_mobile'));
+        $options = array(
+            tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
+            tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
+            tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
+        );
+        $temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
+                    new lang_string('typeoflogin', 'tool_mobile'),
+                    new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
+
+        $temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
+                    new lang_string('forcedurlscheme_key', 'tool_mobile'),
+                    new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS));
 
-    // Type of login.
-    $options = array(
-        tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
-        tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
-        tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
-    );
-    $temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
-                new lang_string('typeoflogin', 'tool_mobile'),
-                new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
+        $ADMIN->add('mobileapp', $temp);
 
-    $temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
-                new lang_string('forcedurlscheme_key', 'tool_mobile'),
-                new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS));
+        // Appearance related settings.
+        $temp = new admin_settingpage('mobileappearance', new lang_string('mobileappearance', 'tool_mobile'));
 
-    $temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
-                new lang_string('smartappbanners', 'tool_mobile'), ''));
+        $temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'tool_mobile'),
+                    new lang_string('configmobilecssurl', 'tool_mobile'), '', PARAM_URL));
 
-    $temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
-                new lang_string('enablesmartappbanners', 'tool_mobile'),
-                new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
+        $temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
+                    new lang_string('smartappbanners', 'tool_mobile'), ''));
 
-    $temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
-                new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
+        $temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
+                    new lang_string('enablesmartappbanners', 'tool_mobile'),
+                    new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
 
-    $ADMIN->add('webservicesettings', $temp);
+        $temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
+                    new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
+
+        $ADMIN->add('mobileapp', $temp);
+    }
 }
index 64657d9..010b1c9 100644 (file)
@@ -174,6 +174,9 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $CFG->enablemobilewebservice = 1;
         $_GET['wstoken'] = $token->token;   // Mock parameters.
 
+        // Even if we force the password change for the current user we should be able to retrieve the key.
+        set_user_preference('auth_forcepasswordchange', 1, $user->id);
+
         $this->setCurrentTimeStart();
         $result = external::get_autologin_key($token->privatetoken);
         $result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
index 1065bd1..b4c724b 100644 (file)
@@ -24,7 +24,7 @@
 
 require_once(__DIR__ . '/../../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
-require_once($CFG->dirroot . '/admin/tool/monitor/lib.php');
+require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/monitor/lib.php');
 
 $courseid = optional_param('courseid', 0, PARAM_INT);
 $action = optional_param('action', '', PARAM_ALPHA);
index ac29055..a86924e 100644 (file)
Binary files a/admin/tool/usertours/amd/build/popper.min.js and b/admin/tool/usertours/amd/build/popper.min.js differ
index 2f37014..20f5d4f 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js and b/admin/tool/usertours/amd/build/tour.min.js differ
index 16119ab..c0fb1d7 100644 (file)
-/**
- * @fileOverview Kickass library to create and place poppers near their reference elements.
- * @version 0.6.4
- * @license
- * Copyright (c) 2016 Federico Zivolo and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-//
-// Cross module loader
-// Supported: Node, AMD, Browser globals
-//
-;(function (root, factory) {
-    if (typeof define === 'function' && define.amd) {
-        // AMD. Register as an anonymous module.
-        define(factory);
-    } else if (typeof module === 'object' && module.exports) {
-        // Node. Does not work with strict CommonJS, but
-        // only CommonJS-like environments that support module.exports,
-        // like Node.
-        module.exports = factory();
-    } else {
-        // Browser globals (root is window)
-        root.Popper = factory();
-    }
-}(this, function () {
-
-    'use strict';
-
-    var root = window;
-
-    // default options
-    var DEFAULTS = {
-        // placement of the popper
-        placement: 'bottom',
-
-        gpuAcceleration: true,
-
-        // shift popper from its origin by the given amount of pixels (can be negative)
-        offset: 0,
-
-        // the element which will act as boundary of the popper
-        boundariesElement: 'viewport',
-
-        // amount of pixel used to define a minimum distance between the boundaries and the popper
-        boundariesPadding: 5,
-
-        // popper will try to prevent overflow following this order,
-        // by default, then, it could overflow on the left and on top of the boundariesElement
-        preventOverflowOrder: ['left', 'right', 'top', 'bottom'],
-
-        // the behavior used by flip to change the placement of the popper
-        flipBehavior: 'flip',
-
-        arrowElement: '[x-arrow]',
-
-        // list of functions used to modify the offsets before they are applied to the popper
-        modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle'],
-
-        modifiersIgnored: [],
-    };
-
-    /**
-     * Create a new Popper.js instance
-     * @constructor Popper
-     * @param {HTMLElement} reference - The reference element used to position the popper
-     * @param {HTMLElement|Object} popper
-     *      The HTML element used as popper, or a configuration used to generate the popper.
-     * @param {String} [popper.tagName='div'] The tag name of the generated popper.
-     * @param {Array} [popper.classNames=['popper']] Array of classes to apply to the generated popper.
-     * @param {Array} [popper.attributes] Array of attributes to apply, specify `attr:value` to assign a value to it.
-     * @param {HTMLElement|String} [popper.parent=window.document.body] The parent element, given as HTMLElement or as query string.
-     * @param {String} [popper.content=''] The content of the popper, it can be text, html, or node; if it is not text, set `contentType` to `html` or `node`.
-     * @param {String} [popper.contentType='text'] If `html`, the `content` will be parsed as HTML. If `node`, it will be appended as-is.
-     * @param {String} [popper.arrowTagName='div'] Same as `popper.tagName` but for the arrow element.
-     * @param {Array} [popper.arrowClassNames='popper__arrow'] Same as `popper.classNames` but for the arrow element.
-     * @param {String} [popper.arrowAttributes=['x-arrow']] Same as `popper.attributes` but for the arrow element.
-     * @param {Object} options
-     * @param {String} [options.placement=bottom]
-     *      Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -right),
-     *      left(-start, -end)`
-     *
-     * @param {HTMLElement|String} [options.arrowElement='[x-arrow]']
-     *      The DOM Node used as arrow for the popper, or a CSS selector used to get the DOM node. It must be child of
-     *      its parent Popper. Popper.js will apply to the given element the style required to align the arrow with its
-     *      reference element.
-     *      By default, it will look for a child node of the popper with the `x-arrow` attribute.
-     *
-     * @param {Boolean} [options.gpuAcceleration=true]
-     *      When this property is set to true, the popper position will be applied using CSS3 translate3d, allowing the
-     *      browser to use the GPU to accelerate the rendering.
-     *      If set to false, the popper will be placed using `top` and `left` properties, not using the GPU.
-     *
-     * @param {Number} [options.offset=0]
-     *      Amount of pixels the popper will be shifted (can be negative).
-     *
-     * @param {String|Element} [options.boundariesElement='viewport']
-     *      The element which will define the boundaries of the popper position, the popper will never be placed outside
-     *      of the defined boundaries (except if `keepTogether` is enabled)
-     *
-     * @param {Number} [options.boundariesPadding=5]
-     *      Additional padding for the boundaries
-     *
-     * @param {Array} [options.preventOverflowOrder=['left', 'right', 'top', 'bottom']]
-     *      Order used when Popper.js tries to avoid overflows from the boundaries, they will be checked in order,
-     *      this means that the last ones will never overflow
-     *
-     * @param {String|Array} [options.flipBehavior='flip']
-     *      The behavior used by the `flip` modifier to change the placement of the popper when the latter is trying to
-     *      overlap its reference element. Defining `flip` as value, the placement will be flipped on
-     *      its axis (`right - left`, `top - bottom`).
-     *      You can even pass an array of placements (eg: `['right', 'left', 'top']` ) to manually specify
-     *      how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left,
-     *      then, if even in its new placement, the popper is overlapping its reference element, it will be moved to top)
-     *
-     * @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle']]
-     *      List of functions used to modify the data before they are applied to the popper, add your custom functions
-     *      to this array to edit the offsets and placement.
-     *      The function should reflect the @params and @returns of preventOverflow
-     *
-     * @param {Array} [options.modifiersIgnored=[]]
-     *      Put here any built-in modifier name you want to exclude from the modifiers list
-     *      The function should reflect the @params and @returns of preventOverflow
-     *
-     * @param {Boolean} [options.removeOnDestroy=false]
-     *      Set to true if you want to automatically remove the popper when you call the `destroy` method.
-     */
-    function Popper(reference, popper, options) {
-        this._reference = reference.jquery ? reference[0] : reference;
-        this.state = { onCreateCalled: false };
-
-        // if the popper variable is a configuration object, parse it to generate an HTMLElement
-        // generate a default popper if is not defined
-        var isNotDefined = typeof popper === 'undefined' || popper === null;
-        var isConfig = popper && Object.prototype.toString.call(popper) === '[object Object]';
-        if (isNotDefined || isConfig) {
-            this._popper = this.parse(isConfig ? popper : {});
-        }
-        // otherwise, use the given HTMLElement as popper
-        else {
-            this._popper = popper.jquery ? popper[0] : popper;
-        }
-
-        // with {} we create a new object with the options inside it
-        this._options = Object.assign({}, DEFAULTS, options);
-
-        // refactoring modifiers' list
-        this._options.modifiers = this._options.modifiers.map(function(modifier){
-            // remove ignored modifiers
-            if (this._options.modifiersIgnored.indexOf(modifier) !== -1) return;
-
-            // set the x-placement attribute before everything else because it could be used to add margins to the popper
-            // margins needs to be calculated to get the correct popper offsets
-            if (modifier === 'applyStyle') {
-                this._popper.setAttribute('x-placement', this._options.placement);
-            }
-
-            // return predefined modifier identified by string or keep the custom one
-            return this.modifiers[modifier] || modifier;
-        }.bind(this));
-
-        // make sure to apply the popper position before any computation
-        this.state.position = this._getPosition(this._popper, this._reference);
-        setStyle(this._popper, { position: this.state.position});
-
-        // determine how we should set the origin of offsets
-        this.state.isParentTransformed = this._getIsParentTransformed(this._popper);
-
-        // fire the first update to position the popper in the right place
-        this.update();
-
-        // setup event listeners, they will take care of update the position in specific situations
-        this._setupEventListeners();
-        return this;
-    }
-
-
-    //
-    // Methods
-    //
-    /**
-     * Destroy the popper
-     * @method
-     * @memberof Popper
-     */
-    Popper.prototype.destroy = function() {
-        this._popper.removeAttribute('x-placement');
-        this._popper.style.left = '';
-        this._popper.style.position = '';
-        this._popper.style.top = '';
-        this._popper.style[getSupportedPropertyName('transform')] = '';
-        this._removeEventListeners();
-
-        // remove the popper if user explicity asked for the deletion on destroy
-        if (this._options.removeOnDestroy) {
-            this._popper.parentNode.removeChild(this._popper);
-        }
-        return this;
-    };
-
-    /**
-     * Updates the position of the popper, computing the new offsets and applying the new style
-     * @method
-     * @memberof Popper
-     */
-    Popper.prototype.update = function() {
-        var data = { instance: this, styles: {} };
-
-        // make sure to apply the popper position before any computation
-        this.state.position = this._getPosition(this._popper, this._reference);
-        setStyle(this._popper, { position: this.state.position});
-
-        // to avoid useless computations we throttle the popper position refresh to 60fps
-        root.requestAnimationFrame(function() {
-            var now = root.performance.now();
-            if(now - this.state.lastFrame <= 16) {
-                // this update fired to early! drop it
-                return;
-            }
-            this.state.lastFrame = now;
-
-            // store placement inside the data object, modifiers will be able to edit `placement` if needed
-            // and refer to _originalPlacement to know the original value
-            data.placement = this._options.placement;
-            data._originalPlacement = this._options.placement;
-
-            // compute the popper and trigger offsets and put them inside data.offsets
-            data.offsets = this._getOffsets(this._popper, this._reference, data.placement);
-
-            // get boundaries
-            data.boundaries = this._getBoundaries(data, this._options.boundariesPadding, this._options.boundariesElement);
-
-            data = this.runModifiers(data, this._options.modifiers);
-
-            if (!isFunction(this.state.createCalback)) {
-                this.state.onCreateCalled = true;
-            }
-            if (!this.state.onCreateCalled) {
-                this.state.onCreateCalled = true;
-                if (isFunction(this.state.createCalback)) {
-                    this.state.createCalback(this);
-                }
-            } else if (isFunction(this.state.updateCallback)) {
-                this.state.updateCallback(data);
-            }
-        }.bind(this));
-    };
-
-    /**
-     * If a function is passed, it will be executed after the initialization of popper with as first argument the Popper instance.
-     * @method
-     * @memberof Popper
-     * @param {Function} callback
-     */
-    Popper.prototype.onCreate = function(callback) {
-        // the createCallbacks return as first argument the popper instance
-        this.state.createCalback = callback;
-        return this;
-    };
-
-    /**
-     * If a function is passed, it will be executed after each update of popper with as first argument the set of coordinates and informations
-     * used to style popper and its arrow.
-     * NOTE: it doesn't get fired on the first call of the `Popper.update()` method inside the `Popper` constructor!
-     * @method
-     * @memberof Popper
-     * @param {Function} callback
-     */
-    Popper.prototype.onUpdate = function(callback) {
-        this.state.updateCallback = callback;
-        return this;
-    };
-
-    /**
-     * Helper used to generate poppers from a configuration file
-     * @method
-     * @memberof Popper
-     * @param config {Object} configuration
-     * @returns {HTMLElement} popper
-     */
-    Popper.prototype.parse = function(config) {
-        var defaultConfig = {
-            tagName: 'div',
-            classNames: [ 'popper' ],
-            attributes: [],
-            parent: root.document.body,
-            content: '',
-            contentType: 'text',
-            arrowTagName: 'div',
-            arrowClassNames: [ 'popper__arrow' ],
-            arrowAttributes: [ 'x-arrow']
-        };
-        config = Object.assign({}, defaultConfig, config);
-
-        var d = root.document;
-
-        var popper = d.createElement(config.tagName);
-        addClassNames(popper, config.classNames);
-        addAttributes(popper, config.attributes);
-        if (config.contentType === 'node') {
-            popper.appendChild(config.content.jquery ? config.content[0] : config.content);
-        }else if (config.contentType === 'html') {
-            popper.innerHTML = config.content;
-        } else {
-            popper.textContent = config.content;
-        }
-
-        if (config.arrowTagName) {
-            var arrow = d.createElement(config.arrowTagName);
-            addClassNames(arrow, config.arrowClassNames);
-            addAttributes(arrow, config.arrowAttributes);
-            popper.appendChild(arrow);
-        }
-
-        var parent = config.parent.jquery ? config.parent[0] : config.parent;
-
-        // if the given parent is a string, use it to match an element
-        // if more than one element is matched, the first one will be used as parent
-        // if no elements are matched, the script will throw an error
-        if (typeof parent === 'string') {
-            parent = d.querySelectorAll(config.parent);
-            if (parent.length > 1) {
-                console.warn('WARNING: the given `parent` query(' + config.parent + ') matched more than one element, the first one will be used');
-            }
-            if (parent.length === 0) {
-                throw 'ERROR: the given `parent` doesn\'t exists!';
-            }
-            parent = parent[0];
-        }
-        // if the given parent is a DOM nodes list or an array of nodes with more than one element,
-        // the first one will be used as parent
-        if (parent.length > 1 && parent instanceof Element === false) {
-            console.warn('WARNING: you have passed as parent a list of elements, the first one will be used');
-            parent = parent[0];
-        }
-
-        // append the generated popper to its parent
-        parent.appendChild(popper);
-
-        return popper;
-
-        /**
-         * Adds class names to the given element
-         * @function
-         * @ignore
-         * @param {HTMLElement} target
-         * @param {Array} classes
-         */
-        function addClassNames(element, classNames) {
-            classNames.forEach(function(className) {
-                element.classList.add(className);
-            });
-        }
-
-        /**
-         * Adds attributes to the given element
-         * @function
-         * @ignore
-         * @param {HTMLElement} target
-         * @param {Array} attributes
-         * @example
-         * addAttributes(element, [ 'data-info:foobar' ]);
-         */
-        function addAttributes(element, attributes) {
-            attributes.forEach(function(attribute) {
-                element.setAttribute(attribute.split(':')[0], attribute.split(':')[1] || '');
-            });
-        }
-
-    };
-
-    /**
-     * Helper used to get the position which will be applied to the popper
-     * @method
-     * @memberof Popper
-     * @param config {HTMLElement} popper element
-     * @returns {HTMLElement} reference element
-     */
-    Popper.prototype._getPosition = function(popper, reference) {
-        var container = getOffsetParent(reference);
-
-        // Decide if the popper will be fixed
-        // If the reference element is inside a fixed context, the popper will be fixed as well to allow them to scroll together
-        var isParentFixed = isFixed(container);
-        return isParentFixed ? 'fixed' : 'absolute';
-    };
-
-    /**
-     * Helper used to determine if the popper's parent is transformed.
-     * @param  {[type]} popper [description]
-     * @return {[type]}        [description]
-     */
-    Popper.prototype._getIsParentTransformed = function(popper) {
-      return isTransformed(popper.parentNode);
-    };
-
-    /**
-     * Get offsets to the popper
-     * @method
-     * @memberof Popper
-     * @access private
-     * @param {Element} popper - the popper element
-     * @param {Element} reference - the reference element (the popper will be relative to this)
-     * @returns {Object} An object containing the offsets which will be applied to the popper
-     */
-    Popper.prototype._getOffsets = function(popper, reference, placement) {
-        placement = placement.split('-')[0];
-        var popperOffsets = {};
-
-        popperOffsets.position = this.state.position;
-        var isParentFixed = popperOffsets.position === 'fixed';
-
-        var isParentTransformed = this.state.isParentTransformed;
-
-        //
-        // Get reference element position
-        //
-        var offsetParent = (isParentFixed && isParentTransformed) ? getOffsetParent(reference) : getOffsetParent(popper);
-        var referenceOffsets = getOffsetRectRelativeToCustomParent(reference, offsetParent, isParentFixed, isParentTransformed);
-
-        //
-        // Get popper sizes
-        //
-        var popperRect = getOuterSizes(popper);
-
-        //
-        // Compute offsets of popper
-        //
-
-        // depending by the popper placement we have to compute its offsets slightly differently
-        if (['right', 'left'].indexOf(placement) !== -1) {
-            popperOffsets.top = referenceOffsets.top + referenceOffsets.height / 2 - popperRect.height / 2;
-            if (placement === 'left') {
-                popperOffsets.left = referenceOffsets.left - popperRect.width;
-            } else {
-                popperOffsets.left = referenceOffsets.right;
-            }
-        } else {
-            popperOffsets.left = referenceOffsets.left + referenceOffsets.width / 2 - popperRect.width / 2;
-            if (placement === 'top') {
-                popperOffsets.top = referenceOffsets.top - popperRect.height;
-            } else {
-                popperOffsets.top = referenceOffsets.bottom;
-            }
-        }
-
-        // Add width and height to our offsets object
-        popperOffsets.width   = popperRect.width;
-        popperOffsets.height  = popperRect.height;
-
-
-        return {
-            popper: popperOffsets,
-            reference: referenceOffsets
-        };
-    };
-
-
-    /**
-     * Setup needed event listeners used to update the popper position
-     * @method
-     * @memberof Popper
-     * @access private
-     */
-    Popper.prototype._setupEventListeners = function() {
-        // NOTE: 1 DOM access here
-        this.state.updateBound = this.update.bind(this);
-        root.addEventListener('resize', this.state.updateBound);
-        // if the boundariesElement is window we don't need to listen for the scroll event
-        if (this._options.boundariesElement !== 'window') {
-            var target = getScrollParent(this._reference);
-            // here it could be both `body` or `documentElement` thanks to Firefox, we then check both
-            if (target === root.document.body || target === root.document.documentElement) {
-                target = root;
-            }
-            target.addEventListener('scroll', this.state.updateBound);
-        }
-    };
 
-    /**
-     * Remove event listeners used to update the popper position
-     * @method
-     * @memberof Popper
-     * @access private
-     */
-    Popper.prototype._removeEventListeners = function() {
-        // NOTE: 1 DOM access here
-        root.removeEventListener('resize', this.state.updateBound);
-        if (this._options.boundariesElement !== 'window') {
-            var target = getScrollParent(this._reference);
-            // here it could be both `body` or `documentElement` thanks to Firefox, we then check both
-            if (target === root.document.body || target === root.document.documentElement) {
-                target = root;
-            }
-            target.removeEventListener('scroll', this.state.updateBound);
-        }
-        this.state.updateBound = null;
-    };
-
-    /**
-     * Computed the boundaries limits and return them
-     * @method
-     * @memberof Popper
-     * @access private
-     * @param {Object} data - Object containing the property "offsets" generated by `_getOffsets`
-     * @param {Number} padding - Boundaries padding
-     * @param {Element} boundariesElement - Element used to define the boundaries
-     * @returns {Object} Coordinates of the boundaries
-     */
-    Popper.prototype._getBoundaries = function(data, padding, boundariesElement) {
-        // NOTE: 1 DOM access here
-        var boundaries = {};
-        var width, height;
-        if (boundariesElement === 'window') {
-            var body = root.document.body,
-                html = root.document.documentElement;
-
-            height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
-            width = Math.max( body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth );
-
-            boundaries = {
-                top: 0,
-                right: width,
-                bottom: height,
-                left: 0
-            };
-        } else if (boundariesElement === 'viewport') {
-            var offsetParent = getOffsetParent(this._popper);
-            var scrollParent = getScrollParent(this._popper);
-            var offsetParentRect = getOffsetRect(offsetParent);
-
-            // if the popper is fixed we don't have to substract scrolling from the boundaries
-            var scrollTop = data.offsets.popper.position === 'fixed' ? 0 : scrollParent.scrollTop;
-            var scrollLeft = data.offsets.popper.position === 'fixed' ? 0 : scrollParent.scrollLeft;
-
-            boundaries = {
-                top: 0 - (offsetParentRect.top - scrollTop),
-                right: root.document.documentElement.clientWidth - (offsetParentRect.left - scrollLeft),
-                bottom: root.document.documentElement.clientHeight - (offsetParentRect.top - scrollTop),
-                left: 0 - (offsetParentRect.left - scrollLeft)
-            };
-        } else {
-            if (getOffsetParent(this._popper) === boundariesElement) {
-                boundaries = {
-                    top: 0,
-                    left: 0,
-                    right: boundariesElement.clientWidth,
-                    bottom: boundariesElement.clientHeight
-                };
-            } else {
-                boundaries = getOffsetRect(boundariesElement);
-            }
-        }
-        boundaries.left += padding;
-        boundaries.right -= padding;
-        boundaries.top = boundaries.top + padding;
-        boundaries.bottom = boundaries.bottom - padding;
-        return boundaries;
-    };
-
-
-    /**
-     * Loop trough the list of modifiers and run them in order, each of them will then edit the data object
-     * @method
-     * @memberof Popper
-     * @access public
-     * @param {Object} data
-     * @param {Array} modifiers
-     * @param {Function} ends
-     */
-    Popper.prototype.runModifiers = function(data, modifiers, ends) {
-        var modifiersToRun = modifiers.slice();
-        if (ends !== undefined) {
-            modifiersToRun = this._options.modifiers.slice(0, getArrayKeyIndex(this._options.modifiers, ends));
-        }
-
-        modifiersToRun.forEach(function(modifier) {
-            if (isFunction(modifier)) {
-                data = modifier.call(this, data);
-            }
-        }.bind(this));
-
-        return data;
-    };
+/*
+* @fileOverview Kickass library to create and place poppers near their reference elements.
+* @version 1.0.0-alpha.3
+* @license
+* Copyright (c) 2016 Federico Zivolo and contributors
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+                    
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define(factory) :
+  (global.Popper = factory());
+}(this, function () { 'use strict';
+
+  /**
+   * The Object.assign() method is used to copy the values of all enumerable own properties from one or more source
+   * objects to a target object. It will return the target object.
+   * This polyfill doesn't support symbol properties, since ES5 doesn't have symbols anyway
+   * Source: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
+   * @function
+   * @ignore
+   */
+  if (!Object.assign) {
+      Object.defineProperty(Object, 'assign', {
+          enumerable: false,
+          configurable: true,
+          writable: true,
+          value: function value(target) {
+              if (target === undefined || target === null) {
+                  throw new TypeError('Cannot convert first argument to object');
+              }
+
+              var to = Object(target);
+              for (var i = 1; i < arguments.length; i++) {
+                  var nextSource = arguments[i];
+                  if (nextSource === undefined || nextSource === null) {
+                      continue;
+                  }
+                  nextSource = Object(nextSource);
+
+                  var keysArray = Object.keys(nextSource);
+                  for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+                      var nextKey = keysArray[nextIndex];
+                      var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
+                      if (desc !== undefined && desc.enumerable) {
+                          to[nextKey] = nextSource[nextKey];
+                      }
+                  }
+              }
+              return to;
+          }
+      });
+  }
+
+  /**
+   * Polyfill for requestAnimationFrame
+   * @function
+   * @ignore
+   */
+  if (!window.requestAnimationFrame) {
+      var lastTime = 0;
+      var vendors = ['ms', 'moz', 'webkit', 'o'];
+      for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+          window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
+          window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
+      }
 
-    /**
-     * Helper used to know if the given modifier depends from another one.
-     * @method
-     * @memberof Popper
-     * @returns {Boolean}
-     */
-
-    Popper.prototype.isModifierRequired = function(requesting, requested) {
-        var index = getArrayKeyIndex(this._options.modifiers, requesting);
-        return !!this._options.modifiers.slice(0, index).filter(function(modifier) {
-            return modifier === requested;
-        }).length;
-    };
+      if (!window.requestAnimationFrame) {
+          window.requestAnimationFrame = function (callback) {
+              var currTime = new Date().getTime();
+              var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+              var id = window.setTimeout(function () {
+                  callback(currTime + timeToCall);
+              }, timeToCall);
+              lastTime = currTime + timeToCall;
+              return id;
+          };
+      }
 
-    //
-    // Modifiers
-    //
-
-    /**
-     * Modifiers list
-     * @namespace Popper.modifiers
-     * @memberof Popper
-     * @type {Object}
-     */
-    Popper.prototype.modifiers = {};
-
-    /**
-     * Apply the computed styles to the popper element
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by `update` method
-     * @returns {Object} The same data object
-     */
-    Popper.prototype.modifiers.applyStyle = function(data) {
-        // apply the final offsets to the popper
-        // NOTE: 1 DOM access here
-        var styles = {
-            position: data.offsets.popper.position
-        };
-
-        // round top and left to avoid blurry text
-        var left = Math.round(data.offsets.popper.left);
-        var top = Math.round(data.offsets.popper.top);
-
-        // if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
-        // we automatically use the supported prefixed version if needed
-        var prefixedProperty;
-        if (this._options.gpuAcceleration && (prefixedProperty = getSupportedPropertyName('transform'))) {
-            styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
-            styles.top = 0;
-            styles.left = 0;
-        }
-        // othwerise, we use the standard `left` and `top` properties
-        else {
-            styles.left =left;
-            styles.top = top;
-        }
-
-        // any property present in `data.styles` will be applied to the popper,
-        // in this way we can make the 3rd party modifiers add custom styles to it
-        // Be aware, modifiers could override the properties defined in the previous
-        // lines of this modifier!
-        Object.assign(styles, data.styles);
-
-        setStyle(this._popper, styles);
-
-        // set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
-        // NOTE: 1 DOM access here
-        this._popper.setAttribute('x-placement', data.placement);
-
-        // if the arrow style has been computed, apply the arrow style
-        if (data.offsets.arrow) {
-            setStyle(data.arrowElement, data.offsets.arrow);
-        }
-
-        // return the data object to allow chaining of other modifiers
-        return data;
-    };
+      if (!window.cancelAnimationFrame) {
+          window.cancelAnimationFrame = function (id) {
+              clearTimeout(id);
+          };
+      }
+  }
+
+  /**
+   * Return the index of the matching object
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Array} arr
+   * @argument prop
+   * @argument value
+   * @returns index or -1
+   */
+  function findIndex(arr, prop, value) {
+    // use filter instead of find because find has less cross-browser support
+    var match = arr.filter(function (obj) {
+      return obj[prop] === value;
+    })[0];
+    return arr.indexOf(match);
+  }
+
+  /**
+   * Returns the offset parent of the given element
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element
+   * @returns {Element} offset parent
+   */
+  function getOffsetParent(element) {
+    // NOTE: 1 DOM access here
+    var offsetParent = element.offsetParent;
+    return !offsetParent || offsetParent.nodeName === 'BODY' ? window.document.documentElement : offsetParent;
+  }
+
+  /**
+   * Get CSS computed property of the given element
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Eement} element
+   * @argument {String} property
+   */
+  function getStyleComputedProperty(element, property) {
+      if (element.nodeType !== 1) {
+          return [];
+      }
+      // NOTE: 1 DOM access here
+      var css = window.getComputedStyle(element, null);
+      return css[property];
+  }
+
+  /**
+   * Returns the parentNode or the host of the element
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element
+   * @returns {Element} parent
+   */
+  function getParentNode(element) {
+    return element.parentNode || element.host;
+  }
+
+  /**
+   * Returns the scrolling parent of the given element
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element
+   * @returns {Element} offset parent
+   */
+  function getScrollParent(element) {
+      if (element === window.document) {
+          // Firefox puts the scrollTOp value on `documentElement` instead of `body`, we then check which of them is
+          // greater than 0 and return the proper element
+          if (window.document.body.scrollTop) {
+              return window.document.body;
+          } else {
+              return window.document.documentElement;
+          }
+      }
 
-    /**
-     * Modifier used to shift the popper on the start or end of its reference element side
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by `update` method
-     * @returns {Object} The data object, properly modified
-     */
-    Popper.prototype.modifiers.shift = function(data) {
-        var placement = data.placement;
-        var basePlacement = placement.split('-')[0];
-        var shiftVariation = placement.split('-')[1];
-
-        // if shift shiftVariation is specified, run the modifier
-        if (shiftVariation) {
-            var reference = data.offsets.reference;
-            var popper = getPopperClientRect(data.offsets.popper);
-
-            var shiftOffsets = {
-                y: {
-                    start:  { top: reference.top },
-                    end:    { top: reference.top + reference.height - popper.height }
-                },
-                x: {
-                    start:  { left: reference.left },
-                    end:    { left: reference.left + reference.width - popper.width }
-                }
-            };
-
-            var axis = ['bottom', 'top'].indexOf(basePlacement) !== -1 ? 'x' : 'y';
-
-            data.offsets.popper = Object.assign(popper, shiftOffsets[axis][shiftVariation]);
-        }
-
-        return data;
-    };
+      // Firefox want us to check `-x` and `-y` variations as well
+      if (['scroll', 'auto'].indexOf(getStyleComputedProperty(element, 'overflow')) !== -1 || ['scroll', 'auto'].indexOf(getStyleComputedProperty(element, 'overflow-x')) !== -1 || ['scroll', 'auto'].indexOf(getStyleComputedProperty(element, 'overflow-y')) !== -1) {
+          // If the detected scrollParent is body, we perform an additional check on its parentNode
+          // in this way we'll get body if the browser is Chrome-ish, or documentElement otherwise
+          // fixes issue #65
+          return element === window.document.body ? getScrollParent(getParentNode(element)) : element;
+      }
+      return getParentNode(element) ? getScrollParent(getParentNode(element)) : element;
+  }
+
+  /**
+   * Get the position of the given element, relative to its offset parent
+   * @method
+   * @memberof Popper.Utils
+   * @param {Element} element
+   * @return {Object} position - Coordinates of the element and its `scrollTop`
+   */
+  function getOffsetRect(element) {
+      var elementRect = {
+          width: element.offsetWidth,
+          height: element.offsetHeight,
+          left: element.offsetLeft,
+          top: element.offsetTop
+      };
+
+      elementRect.right = elementRect.left + elementRect.width;
+      elementRect.bottom = elementRect.top + elementRect.height;
+
+      // position
+      return elementRect;
+  }
+
+  /**
+   * Computed the boundaries limits and return them
+   * @method
+   * @memberof Popper.Utils
+   * @param {Object} data - Object containing the property "offsets" generated by `_getOffsets`
+   * @param {Number} padding - Boundaries padding
+   * @param {Element} boundariesElement - Element used to define the boundaries
+   * @returns {Object} Coordinates of the boundaries
+   */
+  function getBoundaries(popper, data, padding, boundariesElement) {
+      // NOTE: 1 DOM access here
+      var boundaries = {};
+      if (boundariesElement === 'window') {
+          var body = window.document.body;
+          var html = window.document.documentElement;
+          var height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
+          var width = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth);
+
+          boundaries = {
+              top: 0,
+              right: width,
+              bottom: height,
+              left: 0
+          };
+      } else if (boundariesElement === 'viewport') {
+          var offsetParent = getOffsetParent(popper);
+          var scrollParent = getScrollParent(popper);
+          var offsetParentRect = getOffsetRect(offsetParent);
+
+          // if the popper is fixed we don't have to substract scrolling from the boundaries
+          var scrollTop = data.offsets.popper.position === 'fixed' ? 0 : scrollParent.scrollTop;
+          var scrollLeft = data.offsets.popper.position === 'fixed' ? 0 : scrollParent.scrollLeft;
+
+          boundaries = {
+              top: 0 - (offsetParentRect.top - scrollTop),
+              right: window.document.documentElement.clientWidth - (offsetParentRect.left - scrollLeft),
+              bottom: window.document.documentElement.clientHeight - (offsetParentRect.top - scrollTop),
+              left: 0 - (offsetParentRect.left - scrollLeft)
+          };
+      } else {
+          if (getOffsetParent(popper) === boundariesElement) {
+              boundaries = {
+                  top: 0,
+                  left: 0,
+                  right: boundariesElement.clientWidth,
+                  bottom: boundariesElement.clientHeight
+              };
+          } else {
+              boundaries = getOffsetRect(boundariesElement);
+          }
+      }
+      boundaries.left += padding;
+      boundaries.right -= padding;
+      boundaries.top = boundaries.top + padding;
+      boundaries.bottom = boundaries.bottom - padding;
+      return boundaries;
+  }
+
+  /**
+   * Get bounding client rect of given element
+   * @method
+   * @memberof Popper.Utils
+   * @param {HTMLElement} element
+   * @return {Object} client rect
+   */
+  function getBoundingClientRect(element) {
+      var rect = element.getBoundingClientRect();
+      return {
+          left: rect.left,
+          top: rect.top,
+          right: rect.right,
+          bottom: rect.bottom,
+          width: rect.right - rect.left,
+          height: rect.bottom - rect.top
+      };
+  }
+
+  /**
+   * Given an element and one of its parents, return the offset
+   * @method
+   * @memberof Popper.Utils
+   * @param {HTMLElement} element
+   * @param {HTMLElement} parent
+   * @return {Object} rect
+   */
+  function getOffsetRectRelativeToCustomParent(element, parent, fixed, transformed) {
+      var elementRect = getBoundingClientRect(element);
+      var parentRect = getBoundingClientRect(parent);
+
+      if (fixed && !transformed) {
+          var scrollParent = getScrollParent(parent);
+          parentRect.top += scrollParent.scrollTop;
+          parentRect.bottom += scrollParent.scrollTop;
+          parentRect.left += scrollParent.scrollLeft;
+          parentRect.right += scrollParent.scrollLeft;
+      }
 
+      var rect = {
+          top: elementRect.top - parentRect.top,
+          left: elementRect.left - parentRect.left,
+          bottom: elementRect.top - parentRect.top + elementRect.height,
+          right: elementRect.left - parentRect.left + elementRect.width,
+          width: elementRect.width,
+          height: elementRect.height
+      };
+      return rect;
+  }
+
+  /**
+   * Get the outer sizes of the given element (offset size + margins)
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element
+   * @returns {Object} object containing width and height properties
+   */
+  function getOuterSizes(element) {
+      // NOTE: 1 DOM access here
+      var display = element.style.display;
+      var visibility = element.style.visibility;
+
+      element.style.display = 'block';
+      element.style.visibility = 'hidden';
+
+      // original method
+      var styles = window.getComputedStyle(element);
+      var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
+      var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
+      var result = {
+          width: element.offsetWidth + y,
+          height: element.offsetHeight + x
+      };
+
+      // reset element styles
+      element.style.display = display;
+      element.style.visibility = visibility;
+
+      return result;
+  }
+
+  /**
+   * Given the popper offsets, generate an output similar to getBoundingClientRect
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Object} popperOffsets
+   * @returns {Object} ClientRect like output
+   */
+  function getPopperClientRect(popperOffsets) {
+      return Object.assign({}, popperOffsets, {
+          right: popperOffsets.left + popperOffsets.width,
+          bottom: popperOffsets.top + popperOffsets.height
+      });
+  }
+
+  /**
+   * Check if the given element is fixed or is inside a fixed parent
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element
+   * @argument {Element} customContainer
+   * @returns {Boolean} answer to "isFixed?"
+   */
+  function isFixed(element) {
+      if (element === window.document.body) {
+          return false;
+      }
+      if (getStyleComputedProperty(element, 'position') === 'fixed') {
+          return true;
+      }
+      return getParentNode(element) ? isFixed(getParentNode(element)) : element;
+  }
+
+  /**
+   * Helper used to get the position which will be applied to the popper
+   * @method
+   * @memberof Popper.Utils
+   * @param config {HTMLElement} popper element
+   * @returns {HTMLElement} reference element
+   */
+  function getPosition(popper, reference) {
+    var container = getOffsetParent(reference);
+
+    // Decide if the popper will be fixed
+    // If the reference element is inside a fixed context, the popper will be fixed as well to allow them to scroll together
+    var isParentFixed = isFixed(container);
+    return isParentFixed ? 'fixed' : 'absolute';
+  }
+
+  /**
+   * Get the prefixed supported property name
+   * @method
+   * @memberof Popper.Utils
+   * @argument {String} property (camelCase)
+   * @returns {String} prefixed property (camelCase)
+   */
+  function getSupportedPropertyName(property) {
+      var prefixes = ['', 'ms', 'webkit', 'moz', 'o'];
+
+      for (var i = 0; i < prefixes.length; i++) {
+          var toCheck = prefixes[i] ? prefixes[i] + property.charAt(0).toUpperCase() + property.slice(1) : property;
+          if (typeof window.document.body.style[toCheck] !== 'undefined') {
+              return toCheck;
+          }
+      }
+      return null;
+  }
+
+  /**
+   * Check if the given variable is a function
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element - Element to check
+   * @returns {Boolean} answer to: is a function?
+   */
+  function isFunction(functionToCheck) {
+    var getType = {};
+    return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
+  }
+
+  /**
+   * Helper used to know if the given modifier depends from another one.
+   * @method
+   * @memberof Popper.Utils
+   * @returns {Boolean}
+   */
+  function isModifierRequired(modifiers, requesting, requested) {
+      return !!modifiers.filter(function (modifier) {
+          if (modifier.name === requested) {
+              return true;
+          } else if (modifier.name === requesting) {
+              return false;
+          }
+          return false;
+      }).length;
+  }
+
+  /**
+   * Tells if a given input is a number
+   * @method
+   * @memberof Popper.Utils
+   * @param {*} input to check
+   * @return {Boolean}
+   */
+  function isNumeric(n) {
+    return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
+  }
+
+  /**
+   * Check if the given element has transforms applied to itself or a parent
+   * @method
+   * @memberof Popper.Utils
+   * @param  {Element} element
+   * @return {Boolean} answer to "isTransformed?"
+   */
+  function isTransformed(element) {
+      if (element === window.document.body) {
+          return false;
+      }
+      if (getStyleComputedProperty(element, 'transform') !== 'none') {
+          return true;
+      }
+      return getParentNode(element) ? isTransformed(getParentNode(element)) : element;
+  }
+
+  /**
+   * Loop trough the list of modifiers and run them in order, each of them will then edit the data object
+   * @method
+   * @memberof Popper.Utils
+   * @param {Object} data
+   * @param {Array} modifiers
+   * @param {Function} ends
+   */
+  function runModifiers(modifiers, options, data, ends) {
+      var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
+
+      modifiersToRun.forEach(function (modifier) {
+          if (modifier.enabled && isFunction(modifier.function)) {
+              data = modifier.function(data, modifier);
+          }
+      });
+
+      return data;
+  }
+
+  /**
+   * Set the style to the given popper
+   * @method
+   * @memberof Popper.Utils
+   * @argument {Element} element - Element to apply the style to
+   * @argument {Object} styles - Object with a list of properties and values which will be applied to the element
+   */
+  function setStyle(element, styles) {
+      Object.keys(styles).forEach(function (prop) {
+          var unit = '';
+          // add unit if the value is numeric and is one of the following
+          if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
+              unit = 'px';
+          }
+          element.style[prop] = styles[prop] + unit;
+      });
+  }
+
+  /** @namespace Popper.Utils */
+  var Utils = {
+      findIndex: findIndex,
+      getBoundaries: getBoundaries,
+      getBoundingClientRect: getBoundingClientRect,
+      getOffsetParent: getOffsetParent,
+      getOffsetRectRelativeToCustomParent: getOffsetRectRelativeToCustomParent,
+      getOuterSizes: getOuterSizes,
+      getPopperClientRect: getPopperClientRect,
+      getPosition: getPosition,
+      getScrollParent: getScrollParent,
+      getStyleComputedProperty: getStyleComputedProperty,
+      getSupportedPropertyName: getSupportedPropertyName,
+      isFixed: isFixed,
+      isFunction: isFunction,
+      isModifierRequired: isModifierRequired,
+      isNumeric: isNumeric,
+      isTransformed: isTransformed,
+      runModifiers: runModifiers,
+      setStyle: setStyle
+  };
+
+  /**
+   * Get offsets to the popper
+   * @method
+   * @memberof Popper.Utils
+   * @param {Element} popper - the popper element
+   * @param {Element} reference - the reference element (the popper will be relative to this)
+   * @returns {Object} An object containing the offsets which will be applied to the popper
+   */
+  function getOffsets(state, popper, reference, placement) {
+      placement = placement.split('-')[0];
+
+      var popperOffsets = {};
+      popperOffsets.position = state.position;
+
+      var isParentFixed = popperOffsets.position === 'fixed';
+      var isParentTransformed = state.isParentTransformed;
+
+      //
+      // Get reference element position
+      //
+      var offsetParent = getOffsetParent(isParentFixed && isParentTransformed ? reference : popper);
+      var referenceOffsets = getOffsetRectRelativeToCustomParent(reference, offsetParent, isParentFixed, isParentTransformed);
+
+      //
+      // Get popper sizes
+      //
+      var popperRect = getOuterSizes(popper);
+
+      //
+      // Compute offsets of popper
+      //
+
+      // depending by the popper placement we have to compute its offsets slightly differently
+      if (['right', 'left'].indexOf(placement) !== -1) {
+          popperOffsets.top = referenceOffsets.top + referenceOffsets.height / 2 - popperRect.height / 2;
+          if (placement === 'left') {
+              popperOffsets.left = referenceOffsets.left - popperRect.width;
+          } else {
+              popperOffsets.left = referenceOffsets.right;
+          }
+      } else {
+          popperOffsets.left = referenceOffsets.left + referenceOffsets.width / 2 - popperRect.width / 2;
+          if (placement === 'top') {
+              popperOffsets.top = referenceOffsets.top - popperRect.height;
+          } else {
+              popperOffsets.top = referenceOffsets.bottom;
+          }
+      }
 
-    /**
-     * Modifier used to make sure the popper does not overflows from it's boundaries
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by `update` method
-     * @returns {Object} The data object, properly modified
-     */
-    Popper.prototype.modifiers.preventOverflow = function(data) {
-        var order = this._options.preventOverflowOrder;
-        var popper = getPopperClientRect(data.offsets.popper);
-
-        var check = {
-            left: function() {
-                var left = popper.left;
-                if (popper.left < data.boundaries.left) {
-                    left = Math.max(popper.left, data.boundaries.left);
-                }
-                return { left: left };
-            },
-            right: function() {
-                var left = popper.left;
-                if (popper.right > data.boundaries.right) {
-                    left = Math.min(popper.left, data.boundaries.right - popper.width);
-                }
-                return { left: left };
-            },
-            top: function() {
-                var top = popper.top;
-                if (popper.top < data.boundaries.top) {
-                    top = Math.max(popper.top, data.boundaries.top);
-                }
-                return { top: top };
-            },
-            bottom: function() {
-                var top = popper.top;
-                if (popper.bottom > data.boundaries.bottom) {
-                    top = Math.min(popper.top, data.boundaries.bottom - popper.height);
-                }
-                return { top: top };
-            }
-        };
-
-        order.forEach(function(direction) {
-            data.offsets.popper = Object.assign(popper, check[direction]());
-        });
-
-        return data;
-    };
+      // Add width and height to our offsets object
+      popperOffsets.width = popperRect.width;
+      popperOffsets.height = popperRect.height;
+
+      return {
+          popper: popperOffsets,
+          reference: referenceOffsets
+      };
+  }
+
+  /**
+   * Setup needed event listeners used to update the popper position
+   * @method
+   * @memberof Popper.Utils
+   * @private
+   */
+  function setupEventListeners(reference, options, state, updateBound) {
+      // NOTE: 1 DOM access here
+      state.updateBound = updateBound;
+      window.addEventListener('resize', state.updateBound, { passive: true });
+      // if the boundariesElement is window we don't need to listen for the scroll event
+      if (options.boundariesElement !== 'window') {
+          var target = getScrollParent(reference);
+          // here it could be both `body` or `documentElement` thanks to Firefox, we then check both
+          if (target === window.document.body || target === window.document.documentElement) {
+              target = window;
+          }
+          target.addEventListener('scroll', state.updateBound, { passive: true });
+      }
+  }
+
+  /**
+   * Remove event listeners used to update the popper position
+   * @method
+   * @memberof Popper.Utils
+   * @private
+   */
+  function removeEventListeners(reference, state, options) {
+      // NOTE: 1 DOM access here
+      window.removeEventListener('resize', state.updateBound);
+      if (options.boundariesElement !== 'window') {
+          var target = getScrollParent(reference);
+          // here it could be both `body` or `documentElement` thanks to Firefox, we then check both
+          if (target === window.document.body || target === window.document.documentElement) {
+              target = window;
+          }
+          target.removeEventListener('scroll', state.updateBound);
+      }
+      state.updateBound = null;
+      return state;
+  }
+
+  /**
+   * Sorts the modifiers based on their `order` property
+   * @method
+   * @memberof Popper.Utils
+   */
+  function sortModifiers(a, b) {
+      if (a.order < b.order) {
+          return -1;
+      } else if (a.order > b.order) {
+          return 1;
+      }
+      return 0;
+  }
+
+  /**
+   * Apply the computed styles to the popper element
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by `update` method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The same data object
+   */
+  function applyStyle(data) {
+      // apply the final offsets to the popper
+      // NOTE: 1 DOM access here
+      var styles = {
+          position: data.offsets.popper.position
+      };
+
+      // round top and left to avoid blurry text
+      var left = Math.round(data.offsets.popper.left);
+      var top = Math.round(data.offsets.popper.top);
+
+      // if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
+      // we automatically use the supported prefixed version if needed
+      var prefixedProperty = getSupportedPropertyName('transform');
+      if (data.instance.options.gpuAcceleration && prefixedProperty) {
+          styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
+          styles.top = 0;
+          styles.left = 0;
+      }
+      // othwerise, we use the standard `left` and `top` properties
+      else {
+              styles.left = left;
+              styles.top = top;
+          }
+
+      // any property present in `data.styles` will be applied to the popper,
+      // in this way we can make the 3rd party modifiers add custom styles to it
+      // Be aware, modifiers could override the properties defined in the previous
+      // lines of this modifier!
+      Object.assign(styles, data.styles);
+
+      setStyle(data.instance.popper, styles);
+
+      // set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
+      // NOTE: 1 DOM access here
+      data.instance.popper.setAttribute('x-placement', data.placement);
+
+      // if the arrow style has been computed, apply the arrow style
+      if (data.offsets.arrow) {
+          setStyle(data.arrowElement, data.offsets.arrow);
+      }
 
-    /**
-     * Modifier used to make sure the popper is always near its reference
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by _update method
-     * @returns {Object} The data object, properly modified
-     */
-    Popper.prototype.modifiers.keepTogether = function(data) {
-        var popper  = getPopperClientRect(data.offsets.popper);
-        var reference = data.offsets.reference;
-        var f = Math.floor;
-
-        if (popper.right < f(reference.left)) {
-            data.offsets.popper.left = f(reference.left) - popper.width;
-        }
-        if (popper.left > f(reference.right)) {
-            data.offsets.popper.left = f(reference.right);
-        }
-        if (popper.bottom < f(reference.top)) {
-            data.offsets.popper.top = f(reference.top) - popper.height;
-        }
-        if (popper.top > f(reference.bottom)) {
-            data.offsets.popper.top = f(reference.bottom);
-        }
-
-        return data;
-    };
+      return data;
+  }
+
+  /**
+   * Set the x-placement attribute before everything else because it could be used to add margins to the popper
+   * margins needs to be calculated to get the correct popper offsets
+   * @method
+   * @memberof Popper.modifiers
+   * @param {HTMLElement} reference - The reference element used to position the popper
+   * @param {HTMLElement} popper - The HTML element used as popper.
+   * @param {Object} options - Popper.js options
+   */
+  function applyStyleOnLoad(reference, popper, options) {
+      popper.setAttribute('x-placement', options.placement);
+  }
+
+  /**
+   * Modifier used to move the arrows on the edge of the popper to make sure them are always between the popper and the reference element
+   * It will use the CSS outer size of the arrow element to know how many pixels of conjuction are needed
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by update method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The data object, properly modified
+   */
+  function arrow(data, options) {
+      var arrow = options.element;
+
+      // if the arrowElement is a string, suppose it's a CSS selector
+      if (typeof arrow === 'string') {
+          arrow = data.instance.popper.querySelector(arrow);
+      }
 
-    /**
-     * Modifier used to flip the placement of the popper when the latter is starting overlapping its reference element.
-     * Requires the `preventOverflow` modifier before it in order to work.
-     * **NOTE:** This modifier will run all its previous modifiers everytime it tries to flip the popper!
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by _update method
-     * @returns {Object} The data object, properly modified
-     */
-    Popper.prototype.modifiers.flip = function(data) {
-        // check if preventOverflow is in the list of modifiers before the flip modifier.
-        // otherwise flip would not work as expected.
-        if (!this.isModifierRequired(this.modifiers.flip, this.modifiers.preventOverflow)) {
-            console.warn('WARNING: preventOverflow modifier is required by flip modifier in order to work, be sure to include it before flip!');
-            return data;
-        }
-
-        if (data.flipped && data.placement === data._originalPlacement) {
-            // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
-            return data;
-        }
-
-        var placement = data.placement.split('-')[0];
-        var placementOpposite = getOppositePlacement(placement);
-        var variation = data.placement.split('-')[1] || '';
-
-        var flipOrder = [];
-        if(this._options.flipBehavior === 'flip') {
-            flipOrder = [
-                placement,
-                placementOpposite
-            ];
-        } else {
-            flipOrder = this._options.flipBehavior;
-        }
-
-        flipOrder.forEach(function(step, index) {
-            if (placement !== step || flipOrder.length === index + 1) {
-                return;
-            }
-
-            placement = data.placement.split('-')[0];
-            placementOpposite = getOppositePlacement(placement);
-
-            var popperOffsets = getPopperClientRect(data.offsets.popper);
-
-            // this boolean is used to distinguish right and bottom from top and left
-            // they need different computations to get flipped
-            var a = ['right', 'bottom'].indexOf(placement) !== -1;
-
-            // using Math.floor because the reference offsets may contain decimals we are not going to consider here
-            if (
-                a && Math.floor(data.offsets.reference[placement]) > Math.floor(popperOffsets[placementOpposite]) ||
-                !a && Math.floor(data.offsets.reference[placement]) < Math.floor(popperOffsets[placementOpposite])
-            ) {
-                // we'll use this boolean to detect any flip loop
-                data.flipped = true;
-                data.placement = flipOrder[index + 1];
-                if (variation) {
-                    data.placement += '-' + variation;
-                }
-                data.offsets.popper = this._getOffsets(this._popper, this._reference, data.placement).popper;
-
-                data = this.runModifiers(data, this._options.modifiers, this._flip);
-            }
-        }.bind(this));
-        return data;
-    };
+      // if arrow element is not found, don't run the modifier
+      if (!arrow) {
+          return data;
+      }
 
-    /**
-     * Modifier used to add an offset to the popper, useful if you more granularity positioning your popper.
-     * The offsets will shift the popper on the side of its reference element.
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by _update method
-     * @returns {Object} The data object, properly modified
-     */
-    Popper.prototype.modifiers.offset = function(data) {
-        var offset = this._options.offset;
-        var popper  = data.offsets.popper;
-
-        if (data.placement.indexOf('left') !== -1) {
-            popper.top -= offset;
-        }
-        else if (data.placement.indexOf('right') !== -1) {
-            popper.top += offset;
-        }
-        else if (data.placement.indexOf('top') !== -1) {
-            popper.left -= offset;
-        }
-        else if (data.placement.indexOf('bottom') !== -1) {
-            popper.left += offset;
-        }
-        return data;
-    };
+      // the arrow element must be child of its popper
+      if (!data.instance.popper.contains(arrow)) {
+          console.warn('WARNING: `arrowElement` must be child of its popper element!');
+          return data;
+      }
 
-    /**
-     * Modifier used to move the arrows on the edge of the popper to make sure them are always between the popper and the reference element
-     * It will use the CSS outer size of the arrow element to know how many pixels of conjuction are needed
-     * @method
-     * @memberof Popper.modifiers
-     * @argument {Object} data - The data object generated by _update method
-     * @returns {Object} The data object, properly modified
-     */
-    Popper.prototype.modifiers.arrow = function(data) {
-        var arrow  = this._options.arrowElement;
-
-        // if the arrowElement is a string, suppose it's a CSS selector
-        if (typeof arrow === 'string') {
-            arrow = this._popper.querySelector(arrow);
-        }
-
-        // if arrow element is not found, don't run the modifier
-        if (!arrow) {
-            return data;
-        }
-
-        // the arrow element must be child of its popper
-        if (!this._popper.contains(arrow)) {
-            console.warn('WARNING: `arrowElement` must be child of its popper element!');
-            return data;
-        }
-
-        // arrow depends on keepTogether in order to work
-        if (!this.isModifierRequired(this.modifiers.arrow, this.modifiers.keepTogether)) {
-            console.warn('WARNING: keepTogether modifier is required by arrow modifier in order to work, be sure to include it before arrow!');
-            return data;
-        }
-
-        var arrowStyle  = {};
-        var placement   = data.placement.split('-')[0];
-        var popper      = getPopperClientRect(data.offsets.popper);
-        var reference   = data.offsets.reference;
-        var isVertical  = ['left', 'right'].indexOf(placement) !== -1;
-
-        var len         = isVertical ? 'height' : 'width';
-        var side        = isVertical ? 'top' : 'left';
-        var altSide     = isVertical ? 'left' : 'top';
-        var opSide      = isVertical ? 'bottom' : 'right';
-        var arrowSize   = getOuterSizes(arrow)[len];
-
-        //
-        // extends keepTogether behavior making sure the popper and its reference have enough pixels in conjuction
-        //
-
-        // top/left side
-        if (reference[opSide] - arrowSize < popper[side]) {
-            data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowSize);
-        }
-        // bottom/right side
-        if (reference[side] + arrowSize > popper[opSide]) {
-            data.offsets.popper[side] += (reference[side] + arrowSize) - popper[opSide];
-        }
-
-        // compute center of the popper
-        var center = reference[side] + (reference[len] / 2) - (arrowSize / 2);
-
-        // Compute the sideValue using the updated popper offsets
-        var sideValue = center - getPopperClientRect(data.offsets.popper)[side];
-
-        // prevent arrow from being placed not contiguously to its popper
-        sideValue = Math.max(Math.min(popper[len] - arrowSize, sideValue), 0);
-        arrowStyle[side] = sideValue;
-        arrowStyle[altSide] = ''; // make sure to remove any old style from the arrow
-
-        data.offsets.arrow = arrowStyle;
-        data.arrowElement = arrow;
-
-        return data;
-    };
+      // arrow depends on keepTogether in order to work
+      if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) {
+          console.warn('WARNING: keepTogether modifier is required by arrow modifier in order to work, be sure to include it before arrow!');
+          return data;
+      }
 
+      var arrowStyle = {};
+      var placement = data.placement.split('-')[0];
+      var popper = getPopperClientRect(data.offsets.popper);
+      var reference = data.offsets.reference;
+      var isVertical = ['left', 'right'].indexOf(placement) !== -1;
+
+      var len = isVertical ? 'height' : 'width';
+      var side = isVertical ? 'top' : 'left';
+      var altSide = isVertical ? 'left' : 'top';
+      var opSide = isVertical ? 'bottom' : 'right';
+      var arrowSize = getOuterSizes(arrow)[len];
+
+      //
+      // extends keepTogether behavior making sure the popper and its reference have enough pixels in conjuction
+      //
+
+      // top/left side
+      if (reference[opSide] - arrowSize < popper[side]) {
+          data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowSize);
+      }
+      // bottom/right side
+      if (reference[side] + arrowSize > popper[opSide]) {
+          data.offsets.popper[side] += reference[side] + arrowSize - popper[opSide];
+      }
 
-    //
-    // Helpers
-    //
-
-    /**
-     * Get the outer sizes of the given element (offset size + margins)
-     * @function
-     * @ignore
-     * @argument {Element} element
-     * @returns {Object} object containing width and height properties
-     */
-    function getOuterSizes(element) {
-        // NOTE: 1 DOM access here
-        var _display = element.style.display, _visibility = element.style.visibility;
-        element.style.display = 'block'; element.style.visibility = 'hidden';
-        var calcWidthToForceRepaint = element.offsetWidth;
-
-        // original method
-        var styles = root.getComputedStyle(element);
-        var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
-        var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
-        var result = { width: element.offsetWidth + y, height: element.offsetHeight + x };
-
-        // reset element styles
-        element.style.display = _display; element.style.visibility = _visibility;
-        return result;
-    }
+      // compute center of the popper
+      var center = reference[side] + reference[len] / 2 - arrowSize / 2;
+
+      // Compute the sideValue using the updated popper offsets
+      var sideValue = center - getPopperClientRect(data.offsets.popper)[side];
+
+      // prevent arrow from being placed not contiguously to its popper
+      sideValue = Math.max(Math.min(popper[len] - arrowSize, sideValue), 0);
+      arrowStyle[side] = sideValue;
+      arrowStyle[altSide] = ''; // make sure to remove any old style from the arrow
+
+      data.offsets.arrow = arrowStyle;
+      data.arrowElement = arrow;
+
+      return data;
+  }
+
+  /**
+   * Get the opposite placement of the given one/
+   * @method
+   * @memberof Popper.Utils
+   * @argument {String} placement
+   * @returns {String} flipped placement
+   */
+  function getOppositePlacement(placement) {
+    var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
+    return placement.replace(/left|right|bottom|top/g, function (matched) {
+      return hash[matched];
+    });
+  }
+
+  /**
+   * Get the opposite placement variation of the given one/
+   * @method
+   * @memberof Popper.Utils
+   * @argument {String} placement variation
+   * @returns {String} flipped placement variation
+   */
+  function getOppositeVariation(variation) {
+      if (variation === 'end') {
+          return 'start';
+      } else if (variation === 'start') {
+          return 'end';
+      }
+      return variation;
+  }
+
+  /**
+   * Modifier used to flip the placement of the popper when the latter is starting overlapping its reference element.
+   * Requires the `preventOverflow` modifier before it in order to work.
+   * **NOTE:** data.instance modifier will run all its previous modifiers everytime it tries to flip the popper!
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by update method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The data object, properly modified
+   */
+  function flip(data, options) {
+      // check if preventOverflow is in the list of modifiers before the flip modifier.
+      // otherwise flip would not work as expected.
+      if (!isModifierRequired(data.instance.modifiers, 'flip', 'preventOverflow')) {
+          console.warn('WARNING: preventOverflow modifier is required by flip modifier in order to work, be sure to include it before flip!');
+          return data;
+      }
 
-    /**
-     * Get the opposite placement of the given one/
-     * @function
-     * @ignore
-     * @argument {String} placement
-     * @returns {String} flipped placement
-     */
-    function getOppositePlacement(placement) {
-        var hash = {left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
-        return placement.replace(/left|right|bottom|top/g, function(matched){
-            return hash[matched];
-        });
-    }
+      if (data.flipped && data.placement === data.originalPlacement) {
+          // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
+          return data;
+      }
 
-    /**
-     * Given the popper offsets, generate an output similar to getBoundingClientRect
-     * @function
-     * @ignore
-     * @argument {Object} popperOffsets
-     * @returns {Object} ClientRect like output
-     */
-    function getPopperClientRect(popperOffsets) {
-        var offsets = Object.assign({}, popperOffsets);
-        offsets.right = offsets.left + offsets.width;
-        offsets.bottom = offsets.top + offsets.height;
-        return offsets;
-    }
+      var placement = data.placement.split('-')[0];
+      var placementOpposite = getOppositePlacement(placement);
+      var variation = data.placement.split('-')[1] || '';
 
-    /**
-     * Given an array and the key to find, returns its index
-     * @function
-     * @ignore
-     * @argument {Array} arr
-     * @argument keyToFind
-     * @returns index or null
-     */
-    function getArrayKeyIndex(arr, keyToFind) {
-        var i = 0, key;
-        for (key in arr) {
-            if (arr[key] === keyToFind) {
-                return i;
-            }
-            i++;
-        }
-        return null;
-    }
+      var flipOrder = [];
 
-    /**
-     * Get CSS computed property of the given element
-     * @function
-     * @ignore
-     * @argument {Eement} element
-     * @argument {String} property
-     */
-    function getStyleComputedProperty(element, property) {
-        // NOTE: 1 DOM access here
-        var css = root.getComputedStyle(element, null);
-        return css[property];
-    }
+      if (options.behavior === 'flip') {
+          flipOrder = [placement, placementOpposite];
+      } else {
+          flipOrder = options.behavior;
+      }
 
-    /**
-     * Returns the offset parent of the given element
-     * @function
-     * @ignore
-     * @argument {Element} element
-     * @returns {Element} offset parent
-     */
-    function getOffsetParent(element) {
-        // NOTE: 1 DOM access here
-        var offsetParent = element.offsetParent;
-        return offsetParent === root.document.body || !offsetParent ? root.document.documentElement : offsetParent;
-    }
+      flipOrder.forEach(function (step, index) {
+          if (placement !== step || flipOrder.length === index + 1) {
+              return data;
+          }
+
+          placement = data.placement.split('-')[0];
+          placementOpposite = getOppositePlacement(placement);
+
+          var popperOffsets = getPopperClientRect(data.offsets.popper);
+
+          // this boolean is used to distinguish right and bottom from top and left
+          // they need different computations to get flipped
+          var a = ['right', 'bottom'].indexOf(placement) !== -1;
+          var b = ['top', 'bottom'].indexOf(placement) !== -1;
+
+          // using Math.floor because the reference offsets may contain decimals we are not going to consider here
+          var flippedPosition = a && Math.floor(data.offsets.reference[placement]) > Math.floor(popperOffsets[placementOpposite]) || !a && Math.floor(data.offsets.reference[placement]) < Math.floor(popperOffsets[placementOpposite]);
+
+          var flippedVariation = options.flipVariations && (b && variation === 'start' && Math.floor(popperOffsets.left) < Math.floor(data.boundaries.left) || b && variation === 'end' && Math.floor(popperOffsets.right) > Math.floor(data.boundaries.right) || !b && variation === 'start' && Math.floor(popperOffsets.top) < Math.floor(data.boundaries.top) || !b && variation === 'end' && Math.floor(popperOffsets.bottom) > Math.floor(data.boundaries.bottom));
+
+          if (flippedPosition || flippedVariation) {
+              // this boolean to detect any flip loop
+              data.flipped = true;
+
+              if (flippedPosition) {
+                  placement = flipOrder[index + 1];
+              }
+              if (flippedVariation) {
+                  variation = getOppositeVariation(variation);
+              }
+
+              data.placement = placement + (variation ? '-' + variation : '');
+              data.offsets.popper = getOffsets(data.instance.state, data.instance.popper, data.instance.reference, data.placement).popper;
+
+              data = runModifiers(data.instance.modifiers, data.instance.options, data, 'flip');
+          }
+      });
+      return data;
+  }
+
+  /**
+   * Modifier used to make sure the popper is always near its reference element
+   * It cares only about the first axis, you can still have poppers with margin
+   * between the popper and its reference element.
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by update method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The data object, properly modified
+   */
+  function keepTogether(data) {
+      var popper = getPopperClientRect(data.offsets.popper);
+      var reference = data.offsets.reference;
+      var f = Math.floor;
+      var placement = data.placement.split('-')[0];
+
+      if (['top', 'bottom'].indexOf(placement) !== -1) {
+          if (popper.right < f(reference.left)) {
+              data.offsets.popper.left = f(reference.left) - popper.width;
+          }
+          if (popper.left > f(reference.right)) {
+              data.offsets.popper.left = f(reference.right);
+          }
+      } else {
+          if (popper.bottom < f(reference.top)) {
+              data.offsets.popper.top = f(reference.top) - popper.height;
+          }
+          if (popper.top > f(reference.bottom)) {
+              data.offsets.popper.top = f(reference.bottom);
+          }
+      }
 
-    /**
-     * Returns the scrolling parent of the given element
-     * @function
-     * @ignore
-     * @argument {Element} element
-     * @returns {Element} offset parent
-     */
-    function getScrollParent(element) {
-        if (element === root.document) {
-            // Firefox puts the scrollTOp value on `documentElement` instead of `body`, we then check which of them is
-            // greater than 0 and return the proper element
-            if (root.document.body.scrollTop) {
-                return root.document.body;
-            } else {
-                return root.document.documentElement;
-            }
-        }
-
-        // Firefox want us to check `-x` and `-y` variations as well
-        if (
-            ['scroll', 'auto'].indexOf(getStyleComputedProperty(element, 'overflow')) !== -1 ||
-            ['scroll', 'auto'].indexOf(getStyleComputedProperty(element, 'overflow-x')) !== -1 ||
-            ['scroll', 'auto'].indexOf(getStyleComputedProperty(element, 'overflow-y')) !== -1
-        ) {
-            // If the detected scrollParent is body, we perform an additional check on its parentNode
-            // in this way we'll get body if the browser is Chrome-ish, or documentElement otherwise
-            // fixes issue #65
-            return element === root.document.body ? getScrollParent(element.parentNode) : element;
-        }
-        return element.parentNode ? getScrollParent(element.parentNode) : element;
-    }
+      return data;
+  }
+
+  /**
+   * Modifier used to add an offset to the popper, useful if you more granularity positioning your popper.
+   * The offsets will shift the popper on the side of its reference element.
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by update method
+   * @argument {Object} options - Modifiers configuration and options
+   * @argument {Number|String} options.offset=0
+   *      Basic usage allows a number used to nudge the popper by the given amount of pixels.
+   *      You can pass a percentage value as string (eg. `20%`) to nudge by the given percentage (relative to reference element size)
+   *      Other supported units are `vh` and `vw` (relative to viewport)
+   *      Additionally, you can pass a pair of values (eg. `10 20` or `2vh 20%`) to nudge the popper
+   *      on both axis.
+   *      A note about percentage values, if you want to refer a percentage to the popper size instead of the reference element size,
+   *      use `%p` instead of `%` (eg: `20%p`). To make it clearer, you can replace `%` with `%r` and use eg.`10%p 25%r`.
+   *      > **Heads up!** The order of the axis is relative to the popper placement: `bottom` or `top` are `X,Y`, the other are `Y,X`
+   * @returns {Object} The data object, properly modified
+   */
+  function offset(data, options) {
+      var placement = data.placement;
+      var popper = data.offsets.popper;
+
+      var offsets = void 0;
+      if (isNumeric(options.offset)) {
+          offsets = [options.offset, 0];
+      } else {
+          // split the offset in case we are providing a pair of offsets separated
+          // by a blank space
+          offsets = options.offset.split(' ');
+
+          // itherate through each offset to compute them in case they are percentages
+          offsets = offsets.map(function (offset, index) {
+              // separate value from unit
+              var split = offset.match(/(\d*\.?\d*)(.*)/);
+              var value = +split[1];
+              var unit = split[2];
+
+              // use height if placement is left or right and index is 0
+              // otherwise use height
+              // in this way the first offset will use an axis and the second one
+              // will use the other one
+              var useHeight = placement.indexOf('right') !== -1 || placement.indexOf('left') !== -1;
+
+              if (index === 1) {
+                  useHeight = !useHeight;
+              }
+
+              // if is a percentage, we calculate the value of it using as base the
+              // sizes of the reference element
+              if (unit === '%' || unit === '%r') {
+                  var referenceRect = getPopperClientRect(data.offsets.reference);
+                  var len = void 0;
+                  if (useHeight) {
+                      len = referenceRect.height;
+                  } else {
+                      len = referenceRect.width;
+                  }
+                  return len / 100 * value;
+              }
+              // if is a percentage relative to the popper, we calculate the value of it using
+              // as base the sizes of the popper
+              else if (unit === '%p') {
+                      var popperRect = getPopperClientRect(data.offsets.popper);
+                      var _len = void 0;
+                      if (useHeight) {
+                          _len = popperRect.height;
+                      } else {
+                          _len = popperRect.width;
+                      }
+                      return _len / 100 * value;
+                  }
+                  // if is a vh or vw, we calculate the size based on the viewport
+                  else if (unit === 'vh' || unit === 'vw') {
+                          var size = void 0;
+                          if (unit === 'vh') {
+                              size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
+                          } else {
+                              size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
+                          }
+                          return size / 100 * value;
+                      }
+                      // if is an explicit pixel unit, we get rid of the unit and keep the value
+                      else if (unit === 'px') {
+                              return +value;
+                          }
+                          // if is an implicit unit, it's px, and we return just the value
+                          else {
+                                  return +offset;
+                              }
+          });
+      }
 
-    /**
-     * Check if the given element is fixed or is inside a fixed parent
-     * @function
-     * @ignore
-     * @argument {Element} element
-     * @argument {Element} customContainer
-     * @returns {Boolean} answer to "isFixed?"
-     */
-    function isFixed(element) {
-        if (element === root.document.body || element.nodeName === 'HTML') {
-            return false;
-        }
-        if (getStyleComputedProperty(element, 'position') === 'fixed') {
-            return true;
-        }
-        return element.parentNode ? isFixed(element.parentNode) : element;
-    }
+      if (data.placement.indexOf('left') !== -1) {
+          popper.top += offsets[0];
+          popper.left -= offsets[1] || 0;
+      } else if (data.placement.indexOf('right') !== -1) {
+          popper.top += offsets[0];
+          popper.left += offsets[1] || 0;
+      } else if (data.placement.indexOf('top') !== -1) {
+          popper.left += offsets[0];
+          popper.top -= offsets[1] || 0;
+      } else if (data.placement.indexOf('bottom') !== -1) {
+          popper.left += offsets[0];
+          popper.top += offsets[1] || 0;
+      }
+      return data;
+  }
+
+  /**
+   * Modifier used to make sure the popper does not overflows from it's boundaries
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by `update` method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The data object, properly modified
+   */
+  function preventOverflow(data, options) {
+      function shouldMoveWithTarget(direction) {
+          if (!options.moveWithTarget) {
+              return false;
+          }
+          var placement = data.originalPlacement.split('-')[0];
+
+          if (data.flipped && placement === direction || placement === getOppositePlacement(direction)) {
+              return true;
+          }
+          if (placement !== direction && placement !== getOppositePlacement(direction)) {
+              return true;
+          }
 
-    /**
-     * Check if the given element has transforms applied to itself or a parent
-     * @param  {Element} element
-     * @return {Boolean} answer to "isTransformed?"
-     */
-    function isTransformed(element) {
-      if (element === root.document.body) {
           return false;
       }
-      if (getStyleComputedProperty(element, 'transform') !== 'none') {
-          return true;
+      var order = options.priority;
+      var popper = getPopperClientRect(data.offsets.popper);
+
+      var check = {
+          left: function left() {
+              var left = popper.left;
+              if (popper.left < data.boundaries.left && !shouldMoveWithTarget('left')) {
+                  left = Math.max(popper.left, data.boundaries.left);
+              }
+              return { left: left };
+          },
+          right: function right() {
+              var left = popper.left;
+              if (popper.right > data.boundaries.right && !shouldMoveWithTarget('right')) {
+                  left = Math.min(popper.left, data.boundaries.right - popper.width);
+              }
+              return { left: left };
+          },
+          top: function top() {
+              var top = popper.top;
+              if (popper.top < data.boundaries.top && !shouldMoveWithTarget('top')) {
+                  top = Math.max(popper.top, data.boundaries.top);
+              }
+              return { top: top };
+          },
+          bottom: function bottom() {
+              var top = popper.top;
+              if (popper.bottom > data.boundaries.bottom && !shouldMoveWithTarget('bottom')) {
+                  top = Math.min(popper.top, data.boundaries.bottom - popper.height);
+              }
+              return { top: top };
+          }
+      };
+
+      order.forEach(function (direction) {
+          data.offsets.popper = Object.assign(popper, check[direction]());
+      });
+
+      return data;
+  }
+
+  /**
+   * Modifier used to shift the popper on the start or end of its reference element side
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by `update` method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The data object, properly modified
+   */
+  function shift(data) {
+      var placement = data.placement;
+      var basePlacement = placement.split('-')[0];
+      var shiftvariation = placement.split('-')[1];
+
+      // if shift shiftvariation is specified, run the modifier
+      if (shiftvariation) {
+          var reference = data.offsets.reference;
+          var popper = getPopperClientRect(data.offsets.popper);
+
+          var shiftOffsets = {
+              y: {
+                  start: { top: reference.top },
+                  end: { top: reference.top + reference.height - popper.height }
+              },
+              x: {
+                  start: { left: reference.left },
+                  end: { left: reference.left + reference.width - popper.width }
+              }
+          };
+
+          var axis = ['bottom', 'top'].indexOf(basePlacement) !== -1 ? 'x' : 'y';
+
+          data.offsets.popper = Object.assign(popper, shiftOffsets[axis][shiftvariation]);
       }
-      return element.parentNode ? isTransformed(element.parentNode) : element;
-    }
-
-    /**
-     * Set the style to the given popper
-     * @function
-     * @ignore
-     * @argument {Element} element - Element to apply the style to
-     * @argument {Object} styles - Object with a list of properties and values which will be applied to the element
-     */
-    function setStyle(element, styles) {
-        function is_numeric(n) {
-            return (n !== '' && !isNaN(parseFloat(n)) && isFinite(n));
-        }
-        Object.keys(styles).forEach(function(prop) {
-            var unit = '';
-            // add unit if the value is numeric and is one of the following
-            if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && is_numeric(styles[prop])) {
-                unit = 'px';
-            }
-            element.style[prop] = styles[prop] + unit;
-        });
-    }
 
-    /**
-     * Check if the given variable is a function
-     * @function
-     * @ignore
-     * @argument {Element} element - Element to check
-     * @returns {Boolean} answer to: is a function?
-     */
-    function isFunction(functionToCheck) {
-        var getType = {};
-        return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
-    }
-
-    /**
-     * Get the position of the given element, relative to its offset parent
-     * @function
-     * @ignore
-     * @param {Element} element
-     * @return {Object} position - Coordinates of the element and its `scrollTop`
-     */
-    function getOffsetRect(element) {
-        var elementRect = {
-            width: element.offsetWidth,
-            height: element.offsetHeight,
-            left: element.offsetLeft,
-            top: element.offsetTop
-        };
-
-        elementRect.right = elementRect.left + elementRect.width;
-        elementRect.bottom = elementRect.top + elementRect.height;
-
-        // position
-        return elementRect;
-    }
-
-    /**
-     * Get bounding client rect of given element
-     * @function
-     * @ignore
-     * @param {HTMLElement} element
-     * @return {Object} client rect
-     */
-    function getBoundingClientRect(element) {
-        var rect = element.getBoundingClientRect();
-        return {
-            left: rect.left,
-            top: rect.top,
-            right: rect.right,
-            bottom: rect.bottom,
-            width: rect.right - rect.left,
-            height: rect.bottom - rect.top
-        };
-    }
-
-    /**
-     * Given an element and one of its parents, return the offset
-     * @function
-     * @ignore
-     * @param {HTMLElement} element
-     * @param {HTMLElement} parent
-     * @return {Object} rect
-     */
-    function getOffsetRectRelativeToCustomParent(element, parent, fixed, transformed) {
-        var elementRect = getBoundingClientRect(element);
-        var parentRect = getBoundingClientRect(parent);
-
-        if (fixed && !transformed) {
-            var scrollParent = getScrollParent(parent);
-            parentRect.top += scrollParent.scrollTop;
-            parentRect.bottom += scrollParent.scrollTop;
-            parentRect.left += scrollParent.scrollLeft;
-            parentRect.right += scrollParent.scrollLeft;
-        }
-
-        var rect = {
-            top: elementRect.top - parentRect.top ,
-            left: elementRect.left - parentRect.left ,
-            bottom: (elementRect.top - parentRect.top) + elementRect.height,
-            right: (elementRect.left - parentRect.left) + elementRect.width,
-            width: elementRect.width,
-            height: elementRect.height
-        };
-        return rect;
-    }
+      return data;
+  }
+
+  /**
+   * Modifier used to hide the popper when its reference element is outside of the
+   * popper boundaries. It will set an x-hidden attribute which can be used to hide
+   * the popper when its reference is out of boundaries.
+   * @method
+   * @memberof Modifiers
+   * @argument {Object} data - The data object generated by update method
+   * @argument {Object} options - Modifiers configuration and options
+   * @returns {Object} The data object, properly modified
+   */
+  function hide(data) {
+      var refRect = data.offsets.reference;
+      var bound = data.boundaries;
+
+      if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) {
+          data.hide = true;
+          data.instance.popper.setAttribute('x-out-of-boundaries', '');
+      } else {
+          data.hide = false;
+          data.instance.popper.removeAttribute('x-out-of-boundaries');
+      }
 
-    /**
-     * Get the prefixed supported property name
-     * @function
-     * @ignore
-     * @argument {String} property (camelCase)
-     * @returns {String} prefixed property (camelCase)
-     */
-    function getSupportedPropertyName(property) {
-        var prefixes = ['', 'ms', 'webkit', 'moz', 'o'];
-
-        for (var i = 0; i < prefixes.length; i++) {
-            var toCheck = prefixes[i] ? prefixes[i] + property.charAt(0).toUpperCase() + property.slice(1) : property;
-            if (typeof root.document.body.style[toCheck] !== 'undefined') {
-                return toCheck;
-            }
-        }
-        return null;
+      return data;
+  }
+
+  /**
+   * Modifiers are plugins used to alter the behavior of your poppers.
+   * Popper.js uses a set of 7 modifiers to provide all the basic functionalities
+   * needed by the library.
+   *
+   * Each modifier is an object containing several properties listed below.
+   * @namespace Modifiers
+   * @param {Object} modifier - Modifier descriptor
+   * @param {Integer} modifier.order
+   *      The `order` property defines the execution order of the modifiers.
+   *      The built-in modifiers have orders with a gap of 100 units in between,
+   *      this allows you to inject additional modifiers between the existing ones
+   *      without having to redefine the order of all of them.
+   *      The modifiers are executed starting from the one with the lowest order.
+   * @param {Boolean} modifier.enabled - When `true`, the modifier will be used.
+   * @param {Modifiers~modifier} modifier.function - Modifier function.
+   * @param {Modifiers~onLoad} modifier.onLoad - Function executed on popper initalization
+   * @return {Object} data - Each modifier must return the modified `data` object.
+   */
+
+  var modifiersFunctions = {
+    applyStyle: applyStyle,
+    arrow: arrow,
+    flip: flip,
+    keepTogether: keepTogether,
+    offset: offset,
+    preventOverflow: preventOverflow,
+    shift: shift,
+    hide: hide
+  };
+
+  var modifiersOnLoad = {
+    applyStyleOnLoad: applyStyleOnLoad
+  };
+
+  /**
+   * Modifiers can edit the `data` object to change the beheavior of the popper.
+   * This object contains all the informations used by Popper.js to compute the
+   * popper position.
+   * The modifier can edit the data as needed, and then `return` it as result.
+   *
+   * @callback Modifiers~modifier
+   * @param {dataObject} data
+   * @return {dataObject} modified data
+   */
+
+  /**
+   * The `dataObject` is an object containing all the informations used by Popper.js
+   * this object get passed to modifiers and to the `onCreate` and `onUpdate` callbacks.
+   * @name dataObject
+   * @property {Object} data.instance The Popper.js instance
+   * @property {String} data.placement Placement applied to popper
+   * @property {String} data.originalPlacement Placement originally defined on init
+   * @property {Boolean} data.flipped True if popper has been flipped by flip modifier
+   * @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper.
+   * @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier
+   * @property {Object} data.styles Any CSS property defined here will be applied to the popper, it expects the JavaScript nomenclature (eg. `marginBottom`)
+   * @property {Object} data.boundaries Offsets of the popper boundaries
+   * @property {Object} data.offsets The measurements of popper, reference and arrow elements.
+   * @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values
+   * @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values
+   * @property {Object} data.offsets.arro] `top` and `left` offsets, only one of them will be different from 0
+   */
+
+  var classCallCheck = function (instance, Constructor) {
+    if (!(instance instanceof Constructor)) {
+      throw new TypeError("Cannot call a class as a function");
     }
-
-    /**
-     * The Object.assign() method is used to copy the values of all enumerable own properties from one or more source
-     * objects to a target object. It will return the target object.
-     * This polyfill doesn't support symbol properties, since ES5 doesn't have symbols anyway
-     * Source: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
-     * @function
-     * @ignore
-     */
-    if (!Object.assign) {
-        Object.defineProperty(Object, 'assign', {
-            enumerable: false,
-            configurable: true,
-            writable: true,
-            value: function(target) {
-                if (target === undefined || target === null) {
-                    throw new TypeError('Cannot convert first argument to object');
-                }
-
-                var to = Object(target);
-                for (var i = 1; i < arguments.length; i++) {
-                    var nextSource = arguments[i];
-                    if (nextSource === undefined || nextSource === null) {
-                        continue;
-                    }
-                    nextSource = Object(nextSource);
-
-                    var keysArray = Object.keys(nextSource);
-                    for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
-                        var nextKey = keysArray[nextIndex];
-                        var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
-                        if (desc !== undefined && desc.enumerable) {
-                            to[nextKey] = nextSource[nextKey];
-                        }
-                    }
-                }
-                return to;
-            }
-        });
+  };
+
+  var createClass = function () {
+    function defineProperties(target, props) {
+      for (var i = 0; i < props.length; i++) {
+        var descriptor = props[i];
+        descriptor.enumerable = descriptor.enumerable || false;
+        descriptor.configurable = true;
+        if ("value" in descriptor) descriptor.writable = true;
+        Object.defineProperty(target, descriptor.key, descriptor);
+      }
     }
 
-    if (!root.requestAnimationFrame) {
-        var lastTime = 0;
-        var vendors = ['ms', 'moz', 'webkit', 'o'];
-        for(var x = 0; x < vendors.length && !root.requestAnimationFrame; ++x) {
-            root.requestAnimationFrame = root[vendors[x]+'RequestAnimationFrame'];
-            root.cancelAnimationFrame = root[vendors[x]+'CancelAnimationFrame'] || root[vendors[x]+'CancelRequestAnimationFrame'];
-        }
-
-        if (!root.requestAnimationFrame) {
-            root.requestAnimationFrame = function(callback, element) {
-                var currTime = new Date().getTime();
-                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
-                var id = root.setTimeout(function() { callback(currTime + timeToCall); },
-                                           timeToCall);
-                lastTime = currTime + timeToCall;
-                return id;
-            };
-        }
-
-        if (!root.cancelAnimationFrame) {
-            root.cancelAnimationFrame = function(id) {
-                clearTimeout(id);
-            };
-        }
-    }
+    return function (Constructor, protoProps, staticProps) {
+      if (protoProps) defineProperties(Constructor.prototype, protoProps);
+      if (staticProps) defineProperties(Constructor, staticProps);
+      return Constructor;
+    };
+  }();
+
+  // default options
+  var DEFAULTS = {
+      // placement of the popper
+      placement: 'bottom',
+
+      // if true, it uses the CSS 3d transformation to position the popper
+      gpuAcceleration: true,
+
+      // the element which will act as boundary of the popper
+      boundariesElement: 'viewport',
+
+      // amount of pixel used to define a minimum distance between the boundaries and the popper
+      boundariesPadding: 5,
+
+      // list of functions used to modify the offsets before they are applied to the popper
+      modifiers: {
+          shift: {
+              order: 100,
+              enabled: true,
+              function: modifiersFunctions.shift
+          },
+          offset: {
+              order: 200,
+              enabled: true,
+              function: modifiersFunctions.offset,
+              // nudges popper from its origin by the given amount of pixels (can be negative)
+              offset: 0
+          },
+          preventOverflow: {
+              order: 300,
+              enabled: true,
+              function: modifiersFunctions.preventOverflow,
+              // popper will try to prevent overflow following these priorities
+              //  by default, then, it could overflow on the left and on top of the boundariesElement
+              priority: ['left', 'right', 'top', 'bottom']
+          },
+          keepTogether: {
+              order: 400,
+              enabled: true,
+              function: modifiersFunctions.keepTogether
+          },
+          arrow: {
+              order: 500,
+              enabled: true,
+              function: modifiersFunctions.arrow,
+              // selector or node used as arrow
+              element: '[x-arrow]'
+          },
+          flip: {
+              order: 600,
+              enabled: true,
+              function: modifiersFunctions.flip,
+              // the behavior used to change the popper's placement
+              behavior: 'flip'
+          },
+          hide: {
+              order: 700,
+              enabled: true,
+              function: modifiersFunctions.hide
+          },
+          applyStyle: {
+              order: 800,
+              enabled: true,
+              function: modifiersFunctions.applyStyle,
+              onLoad: modifiersOnLoad.applyStyleOnLoad
+          }
+      }
+  };
+
+  /**
+   * Create a new Popper.js instance
+   * @class Popper
+   * @param {HTMLElement} reference - The reference element used to position the popper
+   * @param {HTMLElement} popper - The HTML element used as popper.
+   * @param {Object} options
+   * @param {String} options.placement=bottom
+   *      Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -right),
+   *      left(-start, -end)`
+   *
+   * @param {Boolean} options.gpuAcceleration=true
+   *      When this property is set to true, the popper position will be applied using CSS3 translate3d, allowing the
+   *      browser to use the GPU to accelerate the rendering.
+   *      If set to false, the popper will be placed using `top` and `left` properties, not using the GPU.
+   *
+   * @param {String|Element} options.boundariesElement='viewport'
+   *      The element which will define the boundaries of the popper position, the popper will never be placed outside
+   *      of the defined boundaries (except if `keepTogether` is enabled)
+   *
+   * @param {Number} options.boundariesPadding=5
+   *      Additional padding for the boundaries
+   *
+   * @param {Boolean} options.removeOnDestroy=false
+   *      Set to true if you want to automatically remove the popper when you call the `destroy` method.
+   *
+   * @param {Object} options.modifiers
+   *      List of functions used to modify the data before they are applied to the popper (see source code for default values)
+   *
+   * @param {Object} options.modifiers.arrow - Arrow modifier configuration
+   * @param {HTMLElement|String} options.modifiers.arrow.element='[x-arrow]'
+   *      The DOM Node used as arrow for the popper, or a CSS selector used to get the DOM node. It must be child of
+   *      its parent Popper. Popper.js will apply to the given element the style required to align the arrow with its
+   *      reference element.
+   *      By default, it will look for a child node of the popper with the `x-arrow` attribute.
+   *
+   * @param {Object} options.modifiers.offset - Offset modifier configuration
+   * @param {Number} options.modifiers.offset.offset=0
+   *      Amount of pixels the popper will be shifted (can be negative).
+   *
+   * @param {Object} options.modifiers.preventOverflow - PreventOverflow modifier configuration
+   * @param {Array} [options.modifiers.preventOverflow.priority=['left', 'right', 'top', 'bottom']]
+   *      Priority used when Popper.js tries to avoid overflows from the boundaries, they will be checked in order,
+   *      this means that the last one will never overflow
+   *
+   * @param {Object} options.modifiers.flip - Flip modifier configuration
+   * @param {String|Array} options.modifiers.flip.behavior='flip'
+   *      The behavior used by the `flip` modifier to change the placement of the popper when the latter is trying to
+   *      overlap its reference element. Defining `flip` as value, the placement will be flipped on
+   *      its axis (`right - left`, `top - bottom`).
+   *      You can even pass an array of placements (eg: `['right', 'left', 'top']` ) to manually specify
+   *      how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left,
+   *      then, if even in its new placement, the popper is overlapping its reference element, it will be moved to top)
+   *
+   * @return {Object} instance - The generated Popper.js instance
+   */
+
+  var Popper = function () {
+      function Popper(reference, popper) {
+          var _this = this;
+
+          var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+          classCallCheck(this, Popper);
+          this.Defaults = DEFAULTS;
+
+          // init state
+          this.state = {
+              isDestroyed: false
+          };
+
+          // get reference and popper elements (allow jQuery wrappers)
+          this.reference = reference.jquery ? reference[0] : reference;
+          this.popper = popper.jquery ? popper[0] : popper;
+
+          // with {} we create a new object with the options inside it
+          this.options = Object.assign({}, DEFAULTS, options);
+
+          // refactoring modifiers' list (Object => Array)
+          this.modifiers = Object.keys(DEFAULTS.modifiers).map(function (name) {
+              return Object.assign({ name: name }, DEFAULTS.modifiers[name]);
+          });
+
+          // assign default values to modifiers, making sure to override them with
+          // the ones defined by user
+          this.modifiers = this.modifiers.map(function (defaultConfig) {
+              var userConfig = options.modifiers && options.modifiers[defaultConfig.name] || {};
+              var finalConfig = Object.assign({}, defaultConfig, userConfig);
+              return finalConfig;
+          });
+
+          // add custom modifiers to the modifiers list
+          if (options.modifiers) {
+              Object.keys(options.modifiers).forEach(function (name) {
+                  // take in account only custom modifiers
+                  if (DEFAULTS.modifiers[name] === undefined) {
+                      var modifier = options.modifiers[name];
+                      modifier.name = name;
+                      _this.modifiers.push(modifier);
+                  }
+              });
+          }
+
+          // sort the modifiers by order
+          this.modifiers = this.modifiers.sort(sortModifiers);
+
+          // modifiers have the ability to execute arbitrary code when Popper.js get inited
+          // such code is executed in the same order of its modifier
+          this.modifiers.forEach(function (modifier) {
+              if (modifier.enabled && isFunction(modifier.onLoad)) {
+                  modifier.onLoad(_this.reference, _this.popper, _this.options);
+              }
+          });
+
+          // get the popper position type
+          this.state.position = getPosition(this.popper, this.reference);
+
+          // determine how we should set the origin of offsets
+          this.state.isParentTransformed = isTransformed(this.popper.parentNode);
+
+          // fire the first update to position the popper in the right place
+          this.update(true);
+
+          // setup event listeners, they will take care of update the position in specific situations
+          setupEventListeners(this.reference, this.options, this.state, function () {
+              return _this.update();
+          });
+
+          // make it chainable
+          return this;
+      }
 
-    return Popper;
-}));
+      //
+      // Methods
+      //
+
+      /**
+       * Updates the position of the popper, computing the new offsets and applying the new style
+       * @method
+       * @param {Boolean} isFirstCall
+       *      When true, the onCreate callback is called, otherwise it calls the onUpdate callback
+       * @memberof Popper
+       */
+
+
+      createClass(Popper, [{
+          key: 'update',
+          value: function update(isFirstCall) {
+              var _this2 = this;
+
+              var data = { instance: this, styles: {} };
+
+              // make sure to apply the popper position before any computation
+              this.state.position = getPosition(this.popper, this.reference);
+              setStyle(this.popper, { position: this.state.position });
+
+              // to avoid useless computations we throttle the popper position refresh to 60fps
+              window.requestAnimationFrame(function () {
+                  // if popper is destroyed, don't perform any further update
+                  if (_this2.state.isDestroyed) {
+                      return;
+                  }
+
+                  var now = window.performance.now();
+                  if (now - _this2.state.lastFrame <= 16) {
+                      // this update fired to early! drop it
+                      // but schedule a new one that will be ran at the end of the updates
+                      // chain to make sure everything is proper updated
+                      return _this2.update();
+                  }
+                  _this2.state.lastFrame = now;
+
+                  // store placement inside the data object, modifiers will be able to edit `placement` if needed
+                  // and refer to originalPlacement to know the original value
+                  data.placement = _this2.options.placement;
+                  data.originalPlacement = _this2.options.placement;
+
+                  // compute the popper and reference offsets and put them inside data.offsets
+                  data.offsets = getOffsets(_this2.state, _this2.popper, _this2.reference, data.placement);
+
+                  // get boundaries
+                  data.boundaries = getBoundaries(_this2.popper, data, _this2.options.boundariesPadding, _this2.options.boundariesElement);
+
+                  // run the modifiers
+                  data = runModifiers(_this2.modifiers, _this2.options, data);
+
+                  // the first `update` will call `onCreate` callback
+                  // the other ones will call `onUpdate` callback
+                  if (isFirstCall && isFunction(_this2.state.createCalback)) {
+                      _this2.state.createCalback(data);
+                  } else if (!isFirstCall && isFunction(_this2.state.updateCallback)) {
+                      _this2.state.updateCallback(data);
+                  }
+              });
+          }
+
+          /**
+           * If a function is passed, it will be executed after the initialization of popper with as first argument the Popper instance.
+           * @method
+           * @memberof Popper
+           * @param {createCallback} callback
+           */
+
+      }, {
+          key: 'onCreate',
+          value: function onCreate(callback) {
+              // the createCallbacks return as first argument the popper instance
+              this.state.createCalback = callback;
+              return this;
+          }
+
+          /**
+           * Callback called when the popper is created.
+           * Access Popper.js instance with `data.instance`.
+           * @callback createCallback
+           * @static
+           * @param {dataObject} data
+           */
+
+          /**
+           * If a function is passed, it will be executed after each update of popper with as first argument the set of coordinates and informations
+           * used to style popper and its arrow.
+           * NOTE: it doesn't get fired on the first call of the `Popper.update()` method inside the `Popper` constructor!
+           * @method
+           * @memberof Popper
+           * @param {updateCallback} callback
+           */
+
+      }, {
+          key: 'onUpdate',
+          value: function onUpdate(callback) {
+              this.state.updateCallback = callback;
+              return this;
+          }
+
+          /**
+           * Callback called when the popper is updated, this callback is not called
+           * on the initialization/creation of the popper, but only on subsequent
+           * updates.
+           * Access Popper.js instance with `data.instance`.
+           * @callback updateCallback
+           * @static
+           * @param {dataObject} data
+           */
+
+          /**
+           * Destroy the popper
+           * @method
+           * @memberof Popper
+           */
+
+      }, {
+          key: 'destroy',
+          value: function destroy() {
+              this.state.isDestroyed = true;
+              this.popper.removeAttribute('x-placement');
+              this.popper.style.left = '';
+              this.popper.style.position = '';
+              this.popper.style.top = '';
+              this.popper.style[getSupportedPropertyName('transform')] = '';
+              this.state = removeEventListeners(this.reference, this.state, this.options);
+
+              // remove the popper if user explicity asked for the deletion on destroy
+              // do not use `remove` because IE11 doesn't support it
+              if (this.options.removeOnDestroy) {
+                  this.popper.parentNode.removeChild(this.popper);
+              }
+              return this;
+          }
+
+          /**
+           * Collection of utilities useful when writing custom modifiers
+           * @memberof Popper
+           */
+
+
+          /**
+           * Default Popper.js options
+           * @memberof Popper
+           */
+
+      }]);
+      return Popper;
+  }();
+
+  Popper.Utils = Utils;
+
+  return Popper;
+
+}));
\ No newline at end of file
index 1f7d0c6..3a77250 100644 (file)
@@ -521,6 +521,10 @@ Tour.prototype.normalizeStepConfig = function (stepConfig) {
         attachPoint: 'after'
     }, stepConfig);
 
+    if (stepConfig.attachTo) {
+        stepConfig.attachTo = $(stepConfig.attachTo).first();
+    }
+
     return stepConfig;
 };
 
@@ -612,7 +616,7 @@ Tour.prototype.processStepListeners = function (stepConfig) {
             args: ['click', $.proxy(function (e) {
                 if ($(e.target).parents('[data-flexitour="container"]').length === 0) {
                     // Ignore clicks when they are in the flexitour.
-                    window.setTimeout($.proxy(this.next, this), 100);
+                    window.setTimeout($.proxy(this.next, this), 500);
                 }
             }, this)]
         });
@@ -739,10 +743,10 @@ Tour.prototype.addStepToPage = function (stepConfig) {
         this.positionBackdrop(stepConfig);
 
         if (stepConfig.attachPoint === 'append') {
-            $(stepConfig.attachTo).append(currentStepNode);
+            stepConfig.attachTo.append(currentStepNode);
             this.currentStepNode = currentStepNode;
         } else {
-            this.currentStepNode = currentStepNode.insertAfter($(stepConfig.attachTo));
+            this.currentStepNode = currentStepNode.insertAfter(stepConfig.attachTo);
         }
 
         // Ensure that the step node is positioned.
@@ -754,15 +758,15 @@ Tour.prototype.addStepToPage = function (stepConfig) {
 
         animationTarget.animate({
             scrollTop: this.calculateScrollTop(stepConfig)
-        }).promise().then($.proxy(function () {
+        }).promise().then(function () {
             this.positionStep(stepConfig);
             this.revealStep(stepConfig);
-        }this));
+        }.bind(this));
     } else if (stepConfig.orphan) {
         stepConfig.isOrphan = true;
 
         // This will be appended to the body instead.
-        stepConfig.attachTo = 'body';
+        stepConfig.attachTo = $('body').first();
         stepConfig.attachPoint = 'append';
 
         // Add the backdrop.
@@ -772,17 +776,26 @@ Tour.prototype.addStepToPage = function (stepConfig) {
         currentStepNode.addClass('orphan');
 
         // It lives in the body.
-        $(stepConfig.attachTo).append(currentStepNode);
+        stepConfig.attachTo.append(currentStepNode);
         this.currentStepNode = currentStepNode;
 
         this.currentStepNode.offset(this.calculateStepPositionInPage());
+        this.currentStepNode.css('position', 'fixed');
 
         this.currentStepPopper = new Popper($('body'), this.currentStepNode[0], {
             removeOnDestroy: true,
             placement: stepConfig.placement + '-start',
             arrowElement: '[data-role="arrow"]',
             // Empty the modifiers. We've already placed the step and don't want it moved.
-            modifiers: []
+            modifiers: {
+                hide: {
+                    enabled: false
+                },
+                applyStyle: {
+                    onLoad: null,
+                    enabled: false
+                }
+            }
         });
 
         this.revealStep(stepConfig);
@@ -802,7 +815,7 @@ Tour.prototype.revealStep = function (stepConfig) {
         window.setTimeout($.proxy(function () {
             // After a brief delay, focus again.
             // There seems to be an issue with Jaws where it only reads the dialogue title initially.
-            // This second focus causes it to read the full dialogue.
+            // This second focus helps it to read the full dialogue.
             if (this.currentStepNode) {
                 this.currentStepNode.focus();
             }
@@ -1120,15 +1133,13 @@ Tour.prototype.calculateScrollTop = function (stepConfig) {
 Tour.prototype.calculateStepPositionInPage = function () {
     var viewportHeight = $(window).height();
     var stepHeight = this.currentStepNode.height();
-    var scrollTop = $(window).scrollTop();
 
     var viewportWidth = $(window).width();
     var stepWidth = this.currentStepNode.width();
-    var scrollLeft = $(window).scrollLeft();
 
     return {
-        top: Math.ceil(scrollTop + (viewportHeight - stepHeight) / 2),
-        left: Math.ceil(scrollLeft + (viewportWidth - stepWidth) / 2)
+        top: Math.ceil((viewportHeight - stepHeight) / 2),
+        left: Math.ceil((viewportWidth - stepWidth) / 2)
     };
 };
 
@@ -1166,18 +1177,29 @@ Tour.prototype.positionStep = function (stepConfig) {
     }
 
     var target = this.getStepTarget(stepConfig);
+    var config = {
+        placement: stepConfig.placement + '-start',
+        removeOnDestroy: true,
+        modifiers: {
+            flip: {
+                behaviour: flipBehavior
+            },
+            arrow: {
+                element: '[data-role="arrow"]'
+            }
+        }
+    };
+
+    var boundaryElement = target.closest('section');
+    if (boundaryElement.length) {
+        config.boundariesElement = boundaryElement[0];
+    }
+
     var background = $('[data-flexitour="step-background"]');
     if (background.length) {
         target = background;
     }
-
-    this.currentStepPopper = new Popper(target, content[0], {
-        placement: stepConfig.placement + '-start',
-        removeOnDestroy: true,
-        flipBehavior: flipBehavior,
-        arrowElement: '[data-role="arrow"]',
-        modifiers: ['shift', 'offset', 'preventOverflow', 'keepTogether', this.centerPopper, 'arrow', 'flip', 'applyStyle']
-    });
+    this.currentStepPopper = new Popper(target, content[0], config);
 
     return this;
 };
@@ -1196,9 +1218,9 @@ Tour.prototype.positionBackdrop = function (stepConfig) {
 
         if (stepConfig.zIndex) {
             if (stepConfig.attachPoint === 'append') {
-                $(stepConfig.attachTo).append(backdrop);
+                stepConfig.attachTo.append(backdrop);
             } else {
-                backdrop.insertAfter($(stepConfig.attachTo));
+                backdrop.insertAfter(stepConfig.attachTo);
             }
         } else {
             $('body').append(backdrop);
@@ -1259,10 +1281,10 @@ Tour.prototype.positionBackdrop = function (stepConfig) {
 
             if (stepConfig.zIndex) {
                 if (stepConfig.attachPoint === 'append') {
-                    $(stepConfig.attachTo).append(background);
+                    stepConfig.attachTo.append(background);
                 } else {
-                    fader.insertAfter($(stepConfig.attachTo));
-                    background.insertAfter($(stepConfig.attachTo));
+                    fader.insertAfter(stepConfig.attachTo);
+                    background.insertAfter(stepConfig.attachTo);
                 }
             } else {
                 $('body').append(fader);
@@ -1363,25 +1385,14 @@ Tour.prototype.calculatePosition = function (elem) {
     return null;
 };
 
-Tour.prototype.centerPopper = function (data) {
-    if (!this.isModifierRequired(Tour.prototype.centerPopper, this.modifiers.keepTogether)) {
-        console.warn('WARNING: keepTogether modifier is required by centerPopper modifier in order to work, be sure to include it before arrow!');
-        return data;
-    }
-
-    var placement = data.placement.split('-')[0];
-    var reference = data.offsets.reference;
-    var isVertical = ['left', 'right'].indexOf(placement) !== -1;
-
-    var len = isVertical ? 'height' : 'width';
-    var side = isVertical ? 'top' : 'left';
-
-    data.offsets.popper[side] += Math.max(reference[len] / 2 - data.offsets.popper[len] / 2, 0);
-
-    return data;
-};
-
-Tour.prototype.accessibilityShow = function (stepConfig) {
+/**
+ * Perform accessibility changes for step shown.
+ *
+ * This will add aria-hidden="true" to all siblings and parent siblings.
+ *
+ * @method  accessibilityShow
+ */
+Tour.prototype.accessibilityShow = function () {
     var stateHolder = 'data-has-hidden';
     var attrName = 'aria-hidden';
     var hideFunction = function hideFunction(child) {
@@ -1409,6 +1420,13 @@ Tour.prototype.accessibilityShow = function (stepConfig) {
     });
 };
 
+/**
+ * Perform accessibility changes for step hidden.
+ *
+ * This will remove any newly added aria-hidden="true".
+ *
+ * @method  accessibilityHide
+ */
 Tour.prototype.accessibilityHide = function () {
     var stateHolder = 'data-has-hidden';
     var attrName = 'aria-hidden';
index 47e4633..af94d76 100644 (file)
@@ -241,9 +241,7 @@ class manager {
                 'title' => get_string('importtour', 'tool_usertours'),
             ],
             (object) [
-                'link'  => new \moodle_url('https://moodle.net/mod/data/view.php', [
-                        'id' => 17,
-                    ]),
+                'link'  => new \moodle_url('https://moodle.net/tours'),
                 'linkproperties' => [
                         'target' => '_blank',
                     ],
index 45bad95..3904052 100644 (file)
@@ -4,14 +4,14 @@
     <location>amd/src/tour.js</location>
     <name>Flexitour</name>
     <license>GPLv3</license>
-    <version>0.9.7</version>
+    <version>0.9.9</version>
     <licenseversion>3</licenseversion>
   </library>
   <library>
     <location>amd/src/popper.js</location>
     <name>Popper.js</name>
     <license>MIT</license>
-    <version>v0.6.4</version>
+    <version>v1.0.0-alpha.3</version>
     <licenseversion></licenseversion>
   </library>
 </libraries>
index 2b1ddc0..218ae55 100644 (file)
@@ -34,6 +34,10 @@ defined('MOODLE_INTERNAL') || die();
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 abstract class tree_node {
+
+    /** @var int Counter to be used in {@link tree_node::unique_sql_parameter()}. */
+    protected static $uniquesqlparametercounter = 1;
+
     /**
      * Determines whether this particular item is currently available
      * according to the availability criteria.
@@ -242,10 +246,11 @@ abstract class tree_node {
      * @return SQL code for the parameter, e.g. ':pr1234'
      */
     protected static function unique_sql_parameter(array &$params, $value) {
-        static $count = 1;
+
+        // Note we intentionally do not use self:: here.
+        $count = tree_node::$uniquesqlparametercounter++;
         $unique = 'usp' . $count;
         $params[$unique] = $value;
-        $count++;
         return ':' . $unique;
     }
 }
index 402afdd..b2d3900 100644 (file)
@@ -46,7 +46,7 @@ Feature: Confirm that conditions on completion no longer cause a bug
     And I set the field "Activity or resource" to "Page1"
     And I press "Add restriction..."
     And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
-    And I set the field with xpath "//div[@class='availability-item'][preceding-sibling::div]//select[@name='cm']" to "Page2"
+    And I set the field with xpath "//div[contains(concat(' ', normalize-space(@class), ' '), ' availability-item ')][preceding-sibling::div]//select[@name='cm']" to "Page2"
     And I press "Save and return to course"
     And I should see "Not available unless:" in the ".activity.glossary" "css_element"
     And I should see "The activity Page1 is marked complete" in the ".activity.glossary" "css_element"
index 41d64ea..4885548 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js differ
index e780555..36efbb5 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js differ
index 41d64ea..4885548 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js differ
index 89b1058..e7b418e 100644 (file)
@@ -23,9 +23,10 @@ M.availability_completion.form.initInner = function(cms) {
 
 M.availability_completion.form.getNode = function(json) {
     // Create HTML structure.
-    var html = M.util.get_string('title', 'availability_completion') + ' <span class="availability-group"><label>' +
+    var html = '<span class="col-form-label p-r-1"> ' + M.util.get_string('title', 'availability_completion') + '</span>' +
+               ' <span class="availability-group form-group"><label>' +
             '<span class="accesshide">' + M.util.get_string('label_cm', 'availability_completion') + ' </span>' +
-            '<select name="cm" title="' + M.util.get_string('label_cm', 'availability_completion') + '">' +
+            '<select class="custom-select" name="cm" title="' + M.util.get_string('label_cm', 'availability_completion') + '">' +
             '<option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     for (var i = 0; i < this.cms.length; i++) {
         var cm = this.cms[i];
@@ -34,13 +35,14 @@ M.availability_completion.form.getNode = function(json) {
     }
     html += '</select></label> <label><span class="accesshide">' +
                 M.util.get_string('label_completion', 'availability_completion') +
-            ' </span><select name="e" title="' + M.util.get_string('label_completion', 'availability_completion') + '">' +
+            ' </span><select class="custom-select" ' +
+                            'name="e" title="' + M.util.get_string('label_completion', 'availability_completion') + '">' +
             '<option value="1">' + M.util.get_string('option_complete', 'availability_completion') + '</option>' +
             '<option value="0">' + M.util.get_string('option_incomplete', 'availability_completion') + '</option>' +
             '<option value="2">' + M.util.get_string('option_pass', 'availability_completion') + '</option>' +
             '<option value="3">' + M.util.get_string('option_fail', 'availability_completion') + '</option>' +
             '</select></label></span>';
-    var node = Y.Node.create('<span>' + html + '</span>');
+    var node = Y.Node.create('<span class="form-inline">' + html + '</span>');
 
     // Set initial values.
     if (json.cm !== undefined &&
index 3af4172..abca3e6 100644 (file)
@@ -151,7 +151,7 @@ class frontend extends \core_availability\frontend {
             // NOTE: The fields need to have these weird names in order that they
             // match the standard Moodle form control, otherwise the date selector
             // won't find them.
-            $html .= \html_writer::start_tag('select', array('name' => 'x[' . $field . ']'));
+            $html .= \html_writer::start_tag('select', array('name' => 'x[' . $field . ']', 'class' => 'custom-select'));
             foreach ($options as $key => $value) {
                 $params = array('value' => $key);
                 if ($current[$field] == $key) {
index 7530a28..71cc687 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-debug.js differ
index 8a9bd5d..6a9564d 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js differ
index 7530a28..71cc687 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form.js differ
index e1ee836..30e36fe 100644 (file)
@@ -27,9 +27,10 @@ M.availability_date.form.initInner = function(html, defaultTime) {
 };
 
 M.availability_date.form.getNode = function(json) {
-    var html = M.util.get_string('direction_before', 'availability_date') + ' <span class="availability-group">' +
+    var html = '<span class="col-form-label p-r-1">' +
+                    M.util.get_string('direction_before', 'availability_date') + '</span> <span class="availability-group">' +
             '<label><span class="accesshide">' + M.util.get_string('direction_label', 'availability_date') + ' </span>' +
-            '<select name="direction">' +
+            '<select name="direction" class="custom-select">' +
             '<option value="&gt;=">' + M.util.get_string('direction_from', 'availability_date') + '</option>' +
             '<option value="&lt;">' + M.util.get_string('direction_until', 'availability_date') + '</option>' +
             '</select></label></span> ' + this.html;
index 734994e..adb9f36 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-debug.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-debug.js differ
index 4d4a50c..6df6554 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js differ
index 734994e..adb9f36 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form.js differ
index 0286eb7..7769679 100644 (file)
@@ -35,24 +35,27 @@ M.availability_grade.form.getNode = function(json) {
     this.nodesSoFar++;
 
     // Create HTML structure.
-    var html = '<label>' + M.util.get_string('title', 'availability_grade') + ' <span class="availability-group">' +
-            '<select name="id"><option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>';
+    var html = '<label class="form-group"><span class="p-r-1">' + M.util.get_string('title', 'availability_grade') + '</span> ' +
+            '<span class="availability-group">' +
+            '<select name="id" class="custom-select"><option value="0">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     for (var i = 0; i < this.grades.length; i++) {
         var grade = this.grades[i];
         // String has already been escaped using format_string.
         html += '<option value="' + grade.id + '">' + grade.name + '</option>';
     }
-    html += '</select></span></label> <span class="availability-group">' +
-            '<label><input type="checkbox" name="min"/>' + M.util.get_string('option_min', 'availability_grade') +
+    html += '</select></span></label> <br><span class="availability-group form-group">' +
+            '<label><input type="checkbox" class="form-check-input m-x-1" name="min"/>' +
+            M.util.get_string('option_min', 'availability_grade') +
             '</label> <label><span class="accesshide">' + M.util.get_string('label_min', 'availability_grade') +
-            '</span><input type="text" name="minval" title="' +
-            M.util.get_string('label_min', 'availability_grade') + '"/></label>%</span>' +
-            '<span class="availability-group">' +
-            '<label><input type="checkbox" name="max"/>' + M.util.get_string('option_max', 'availability_grade') +
+            '</span><input type="text" class="form-control m-x-1" name="minval" title="' +
+            M.util.get_string('label_min', 'availability_grade') + '"/></label>%</span><br>' +
+            '<span class="availability-group form-group">' +
+            '<label><input type="checkbox" class="form-check-input m-x-1" name="max"/>' +
+            M.util.get_string('option_max', 'availability_grade') +
             '</label> <label><span class="accesshide">' + M.util.get_string('label_max', 'availability_grade') +
-            '</span><input type="text" name="maxval" title="' +
+            '</span><input type="text" class="form-control m-x-1" name="maxval" title="' +
             M.util.get_string('label_max', 'availability_grade') + '"/></label>%</span>';
-    var node = Y.Node.create('<span>' + html + '</span>');
+    var node = Y.Node.create('<div class="d-inline-block form-inline">' + html + '</div>');
 
     // Set initial values.
     if (json.id !== undefined &&
index 9ddaa66..e6272f9 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-debug.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-debug.js differ
index 040d395..52658fe 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js differ
index 9ddaa66..e6272f9 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form.js differ
index 84eaa33..6c25361 100644 (file)
@@ -31,8 +31,9 @@ M.availability_group.form.initInner = function(groups) {
 
 M.availability_group.form.getNode = function(json) {
     // Create HTML structure.
-    var html = '<label>' + M.util.get_string('title', 'availability_group') + ' <span class="availability-group">' +
-            '<select name="id">' +
+    var html = '<label><span class="p-r-1">' + M.util.get_string('title', 'availability_group') + '</span> ' +
+            '<span class="availability-group">' +
+            '<select name="id" class="custom-select">' +
             '<option value="choose">' + M.util.get_string('choosedots', 'moodle') + '</option>' +
             '<option value="any">' + M.util.get_string('anygroup', 'availability_group') + '</option>';
     for (var i = 0; i < this.groups.length; i++) {
@@ -41,7 +42,7 @@ M.availability_group.form.getNode = function(json) {
         html += '<option value="' + group.id + '">' + group.name + '</option>';
     }
     html += '</select></span></label>';
-    var node = Y.Node.create('<span>' + html + '</span>');
+    var node = Y.Node.create('<span class="form-inline">' + html + '</span>');
 
     // Set initial values (leave default 'choose' if creating afresh).
     if (json.creating === undefined) {
index c60c681..6dd1a72 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-debug.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-debug.js differ
index 5f1bcbc..ebd80d8 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js differ
index c60c681..6dd1a72 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form.js differ
index ad3135e..4152385 100644 (file)
@@ -31,8 +31,9 @@ M.availability_grouping.form.initInner = function(groupings) {
 
 M.availability_grouping.form.getNode = function(json) {
     // Create HTML structure.
-    var html = '<label>' + M.util.get_string('title', 'availability_grouping') + ' <span class="availability-group">' +
-            '<select name="id">' +
+    var html = '<label><span class="p-r-1">' + M.util.get_string('title', 'availability_grouping') + '</span> ' +
+            '<span class="availability-group">' +
+            '<select name="id" class="custom-select">' +
             '<option value="choose">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     for (var i = 0; i < this.groupings.length; i++) {
         var grouping = this.groupings[i];
@@ -40,7 +41,7 @@ M.availability_grouping.form.getNode = function(json) {
         html += '<option value="' + grouping.id + '">' + grouping.name + '</option>';
     }
     html += '</select></span></label>';
-    var node = Y.Node.create('<span>' + html + '</span>');
+    var node = Y.Node.create('<span class="form-inline">' + html + '</span>');
 
     // Set initial value if specified.
     if (json.id !== undefined &&
index 9909ea2..f47ec11 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-debug.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-debug.js differ
index 303b419..f870dda 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js differ
index 9909ea2..f47ec11 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form.js differ
index db73335..dcab53f 100644 (file)
@@ -33,8 +33,9 @@ M.availability_profile.form.initInner = function(standardFields, customFields) {
 
 M.availability_profile.form.getNode = function(json) {
     // Create HTML structure.
-    var html = '<span class="availability-group"><label>' + M.util.get_string('conditiontitle', 'availability_profile') + ' ' +
-            '<select name="field">' +
+    var html = '<span class="availability-group"><label><span class="p-r-1">' +
+            M.util.get_string('conditiontitle', 'availability_profile') + '</span> ' +
+            '<select name="field" class="custom-select">' +
             '<option value="choose">' + M.util.get_string('choosedots', 'moodle') + '</option>';
     var fieldInfo;
     for (var i = 0; i < this.standardFields.length; i++) {
@@ -48,7 +49,8 @@ M.availability_profile.form.getNode = function(json) {
         html += '<option value="cf_' + fieldInfo.field + '">' + fieldInfo.display + '</option>';
     }
     html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_operator', 'availability_profile') +
-            ' </span><select name="op" title="' + M.util.get_string('label_operator', 'availability_profile') + '">';
+            ' </span><select name="op" title="' + M.util.get_string('label_operator', 'availability_profile') + '"' +
+                     ' class="custom-select">';
     var operators = ['isequalto', 'contains', 'doesnotcontain', 'startswith', 'endswith',
             'isempty', 'isnotempty'];
     for (i = 0; i < operators.length; i++) {
@@ -56,9 +58,9 @@ M.availability_profile.form.getNode = function(json) {
                 M.util.get_string('op_' + operators[i], 'availability_profile') + '</option>';
     }
     html += '</select></label> <label><span class="accesshide">' + M.util.get_string('label_value', 'availability_profile') +
-            '</span><input name="value" type="text" style="width: 10em" title="' +
+            '</span><input name="value" type="text" class="form-control" style="width: 10em" title="' +
             M.util.get_string('label_value', 'availability_profile') + '"/></label></span>';
-    var node = Y.Node.create('<span>' + html + '</span>');
+    var node = Y.Node.create('<span class="form-inline">' + html + '</span>');
 
     // Set initial values if specified.
     if (json.sf !== undefined &&
index ab6c30c..1b131a9 100644 (file)
@@ -675,6 +675,63 @@ class tree_testcase extends \advanced_testcase {
                 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, array(true, false))));
     }
 
+    /**
+     * Tests the behaviour of the counter in unique_sql_parameter().
+     *
+     * There was a problem with static counters used to implement a sequence of
+     * parameter placeholders (MDL-53481). As always with static variables, it
+     * is a bit tricky to unit test the behaviour reliably as it depends on the
+     * actual tests executed and also their order.
+     *
+     * To minimise risk of false expected behaviour, this test method should be
+     * first one where {@link core_availability\tree::get_user_list_sql()} is
+     * used. We also use higher number of condition instances to increase the
+     * risk of the counter collision, should there remain a problem.
+     */
+    public function test_unique_sql_parameter_behaviour() {
+        global $DB;
+        $this->resetAfterTest();
+        $generator = $this->getDataGenerator();
+
+        // Create a test course with multiple groupings and groups and a student in each of them.
+        $course = $generator->create_course();
+        $user = $generator->create_user();
+        $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
+        $generator->enrol_user($user->id, $course->id, $studentroleid);
+        // The total number of groupings and groups must not be greater than 61.
+        // There is a limit in MySQL on the max number of joined tables.
+        $groups = [];
+        for ($i = 0; $i < 25; $i++) {
+            $group = $generator->create_group(array('courseid' => $course->id));
+            groups_add_member($group, $user);
+            $groups[] = $group;
+        }
+        $groupings = [];
+        for ($i = 0; $i < 25; $i++) {
+            $groupings[] = $generator->create_grouping(array('courseid' => $course->id));
+        }
+        foreach ($groupings as $grouping) {
+            foreach ($groups as $group) {
+                groups_assign_grouping($grouping->id, $group->id);
+            }
+        }
+        $info = new \core_availability\mock_info($course);
+
+        // Make a huge tree with 'AND' of all groups and groupings conditions.
+        $conditions = [];
+        foreach ($groups as $group) {
+            $conditions[] = \availability_group\condition::get_json($group->id);
+        }
+        foreach ($groupings as $groupingid) {
+            $conditions[] = \availability_grouping\condition::get_json($grouping->id);
+        }
+        shuffle($conditions);
+        $tree = new tree(tree::get_root_json($conditions));
+        list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
+        // This must not throw exception.
+        $DB->fix_sql_params($sql, $params);
+    }
+
     /**
      * Tests get_user_list_sql.
      */
index 40b36ad..bfdb2fc 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js differ
index 2668730..7b569c3 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js differ
index 40b36ad..bfdb2fc 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js differ
index e99abb0..a2cc382 100644 (file)
@@ -356,21 +356,25 @@ M.core_availability.List = function(json, root, parentRoot) {
     // Create DIV structure (without kids).
     this.node = Y.Node.create('<div class="availability-list"><h3 class="accesshide"></h3>' +
             '<div class="availability-inner">' +
-            '<div class="availability-header">' + M.util.get_string('listheader_sign_before', 'availability') +
+            '<div class="availability-header"><span class="p-l-1">' +
+            M.util.get_string('listheader_sign_before', 'availability') + '</span>' +
             ' <label><span class="accesshide">' + M.util.get_string('label_sign', 'availability') +
-            ' </span><select class="availability-neg" title="' + M.util.get_string('label_sign', 'availability') + '">' +
+            ' </span><select class="availability-neg custom-select m-x-1"' +
+            ' title="' + M.util.get_string('label_sign', 'availability') + '">' +
             '<option value="">' + M.util.get_string('listheader_sign_pos', 'availability') + '</option>' +
             '<option value="!">' + M.util.get_string('listheader_sign_neg', 'availability') + '</option></select></label> ' +
             '<span class="availability-single">' + M.util.get_string('listheader_single', 'availability') + '</span>' +
             '<span class="availability-multi">' + M.util.get_string('listheader_multi_before', 'availability') +
             ' <label><span class="accesshide">' + M.util.get_string('label_multi', 'availability') + ' </span>' +
-            '<select class="availability-op" title="' + M.util.get_string('label_multi', 'availability') + '"><option value="&">' +
+            '<select class="availability-op custom-select m-x-1"' +
+            ' title="' + M.util.get_string('label_multi', 'availability') + '"><option value="&">' +
             M.util.get_string('listheader_multi_and', 'availability') + '</option>' +
             '<option value="|">' + M.util.get_string('listheader_multi_or', 'availability') + '</option></select></label> ' +
             M.util.get_string('listheader_multi_after', 'availability') + '</span></div>' +
             '<div class="availability-children"></div>' +
-            '<div class="availability-none">' + M.util.get_string('none', 'moodle') + '</div>' +
-            '<div class="availability-button"></div></div></div>');
+            '<div class="availability-none"><span class="p-x-1">' + M.util.get_string('none', 'moodle') + '</span></div>' +
+            '<div class="clearfix m-t-1"></div>' +
+            '<div class="availability-button"></div></div><div class="clearfix"></div></div>');
     if (!root) {
         this.node.addClass('availability-childlist');
     }
@@ -404,12 +408,12 @@ M.core_availability.List = function(json, root, parentRoot) {
         noneNode.appendChild(deleteIcon.span);
 
         // Also if it's not the root, none is actually invalid, so add a label.
-        noneNode.appendChild(Y.Node.create('<span class="label label-warning">' +
+        noneNode.appendChild(Y.Node.create('<span class="m-t-1 label label-warning">' +
                 M.util.get_string('invalid', 'availability') + '</span>'));
     }
 
     // Create the button and add it.
-    var button = Y.Node.create('<button type="button" class="btn btn-default">' +
+    var button = Y.Node.create('<button type="button" class="btn btn-default m-t-1">' +
             M.util.get_string('addrestriction', 'availability') + '</button>');
     button.on("click", function() {
         this.clickAdd();
@@ -665,7 +669,7 @@ M.core_availability.List.prototype.deleteDescendant = function(descendant) {
  */
 M.core_availability.List.prototype.clickAdd = function() {
     var content = Y.Node.create('<div>' +
-            '<ul class="list-unstyled"></ul>' +
+            '<ul class="list-unstyled container-fluid"></ul>' +
             '<div class="availability-buttons mdl-align">' +
             '<button type="button" class="btn btn-default">' + M.util.get_string('cancel', 'moodle') +
             '</button></div></div>');
@@ -681,25 +685,25 @@ M.core_availability.List.prototype.clickAdd = function() {
             continue;
         }
         // Add entry for plugin.
-        li = Y.Node.create('<li class="clearfix"></li>');
+        li = Y.Node.create('<li class="clearfix row"></li>');
         id = 'availability_addrestriction_' + type;
-        button = Y.Node.create('<button type="button" class="btn btn-default"' +
+        button = Y.Node.create('<button type="button" class="btn btn-default col-xs-6"' +
                 'id="' + id + '">' + M.util.get_string('title', 'availability_' + type) + '</button>');
         button.on('click', this.getAddHandler(type, dialogRef), this);
         li.appendChild(button);
-        label = Y.Node.create('<label for="' + id + '">' +
+        label = Y.Node.create('<label for="' + id + '" class="col-xs-6">' +
                 M.util.get_string('description', 'availability_' + type) + '</label>');
         li.appendChild(label);
         ul.appendChild(li);
     }
     // Extra entry for lists.
-    li = Y.Node.create('<li class="clearfix"></li>');
+    li = Y.Node.create('<li class="clearfix row"></li>');
     id = 'availability_addrestriction_list_';
-    button = Y.Node.create('<button type="button" class="btn btn-default"' +
+    button = Y.Node.create('<button type="button" class="btn btn-default col-xs-6"' +
             'id="' + id + '">' + M.util.get_string('condition_group', 'availability') + '</button>');
     button.on('click', this.getAddHandler(null, dialogRef), this);
     li.appendChild(button);
-    label = Y.Node.create('<label for="' + id + '">' +
+    label = Y.Node.create('<label for="' + id + '" class="col-xs-6">' +
             M.util.get_string('condition_group_info', 'availability') + '</label>');
     li.appendChild(label);
     ul.appendChild(li);
@@ -895,7 +899,7 @@ M.core_availability.Item = function(json, root) {
         this.pluginNode.addClass('availability_' + json.type);
     }
 
-    this.node = Y.Node.create('<div class="availability-item"><h3 class="accesshide"></h3></div>');
+    this.node = Y.Node.create('<div class="availability-item d-inline-block"><h3 class="accesshide"></h3></div>');
 
     // Add eye icon if required. This icon is added for root items, but may be
     // hidden depending on the selected list operator.
@@ -918,7 +922,7 @@ M.core_availability.Item = function(json, root) {
 
     // Add the invalid marker (empty).
     this.node.appendChild(document.createTextNode(' '));
-    this.node.appendChild(Y.Node.create('<span class="label label-warning"/>'));
+    this.node.appendChild(Y.Node.create('<span class="m-t-1 label label-warning"/>'));
 };
 
 /**
@@ -1046,7 +1050,7 @@ M.core_availability.Item.prototype.pluginNode = null;
  */
 M.core_availability.EyeIcon = function(individual, shown) {
     this.individual = individual;
-    this.span = Y.Node.create('<a class="availability-eye" href="#" role="button">');
+    this.span = Y.Node.create('<a class="availability-eye col-form-label" href="#" role="button">');
     var icon = Y.Node.create('<img />');
     this.span.appendChild(icon);
 
@@ -1126,7 +1130,7 @@ M.core_availability.EyeIcon.prototype.isHidden = function() {
  * @param {M.core_availability.Item|M.core_availability.List} toDelete Thing to delete
  */
 M.core_availability.DeleteIcon = function(toDelete) {
-    this.span = Y.Node.create('<a class="availability-delete" href="#" title="' +
+    this.span = Y.Node.create('<a class="d-inline-block col-form-label availability-delete p-x-1" href="#" title="' +
             M.util.get_string('delete', 'moodle') + '" role="button">');
     var img = Y.Node.create('<img src="' + M.util.image_url('t/delete', 'core') +
             '" alt="' + M.util.get_string('delete', 'moodle') + '" />');
index a3901e5..e5e93c9 100644 (file)
@@ -290,7 +290,8 @@ class core_backup_renderer extends plugin_renderer_base {
             } else {
                 $html .= $selectacategoryhtml;
             }
-            $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('continue'))));
+            $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
+            $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs));
             $html .= html_writer::end_tag('div');
             $html .= html_writer::end_tag('form');
         }
@@ -306,7 +307,8 @@ class core_backup_renderer extends plugin_renderer_base {
                 backup::TARGET_CURRENT_ADDING, array('checked' => 'checked'));
             $html .= $this->backup_detail_input(get_string('restoretocurrentcoursedeleting', 'backup'), 'radio', 'target',
                 backup::TARGET_CURRENT_DELETING);
-            $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('continue'))));
+            $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
+            $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs));
             $html .= html_writer::end_tag('div');
             $html .= html_writer::end_tag('form');
         }
@@ -340,7 +342,8 @@ class core_backup_renderer extends plugin_renderer_base {
             } else {
                 $html .= $selectacoursehtml;
             }
-            $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('continue'))));
+            $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
+            $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs));
             $html .= html_writer::end_tag('div');
             $html .= html_writer::end_tag('form');
         }
@@ -371,7 +374,8 @@ class core_backup_renderer extends plugin_renderer_base {
         $html .= html_writer::start_tag('div', array('class' => 'ics-existing-course backup-section'));
         $html .= $this->output->heading(get_string('importdatafrom'), 2, array('class' => 'header'));
         $html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
-        $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('continue'))));
+        $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
+        $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs));
         $html .= html_writer::end_tag('div');
         $html .= html_writer::end_tag('form');
         $html .= html_writer::end_tag('div');
@@ -470,9 +474,11 @@ class core_backup_renderer extends plugin_renderer_base {
     public function substage_buttons($haserrors) {
         $output  = html_writer::start_tag('div', array('continuebutton'));
         if (!$haserrors) {
-            $output .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('continue')));
+            $attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
+            $output .= html_writer::empty_tag('input', $attrs);
         }
-        $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'cancel', 'value' => get_string('cancel')));
+        $attrs = array('type' => 'submit', 'name' => 'cancel', 'value' => get_string('cancel'), 'class' => 'btn btn-secondary');
+        $output .= html_writer::empty_tag('input', $attrs);
         $output .= html_writer::end_tag('div');
         return $output;
     }
@@ -601,7 +607,7 @@ class core_backup_renderer extends plugin_renderer_base {
     public function render_restore_course_search(restore_course_search $component) {
         $url = $component->get_url();
 
-        $output = html_writer::start_tag('div', array('class' => 'restore-course-search'));
+        $output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline m-b-1'));
         $output .= html_writer::start_tag('div', array('class' => 'rcs-results'));
 
         $table = new html_table();
@@ -641,8 +647,20 @@ class core_backup_renderer extends plugin_renderer_base {
         $output .= html_writer::end_tag('div');
 
         $output .= html_writer::start_tag('div', array('class' => 'rcs-search'));
-        $output .= html_writer::empty_tag('input', array('type' => 'text', 'name' => restore_course_search::$VAR_SEARCH, 'value' => $component->get_search()));
-        $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'searchcourses', 'value' => get_string('search')));
+        $attrs = array(
+            'type' => 'text',
+            'name' => restore_course_search::$VAR_SEARCH,
+            'value' => $component->get_search(),
+            'class' => 'form-control'
+        );
+        $output .= html_writer::empty_tag('input', $attrs);
+        $attrs = array(
+            'type' => 'submit',
+            'name' => 'searchcourses',
+            'value' => get_string('search'),
+            'class' => 'btn btn-secondary'
+        );
+        $output .= html_writer::empty_tag('input', $attrs);
         $output .= html_writer::end_tag('div');
 
         $output .= html_writer::end_tag('div');
@@ -663,8 +681,20 @@ class core_backup_renderer extends plugin_renderer_base {
             $output .= $this->output->notification(get_string('nomatchingcourses', 'backup'));
 
             $output .= html_writer::start_tag('div', array('class' => 'ics-search'));
-            $output .= html_writer::empty_tag('input', array('type' => 'text', 'name' => restore_course_search::$VAR_SEARCH, 'value' => $component->get_search()));
-            $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'searchcourses', 'value' => get_string('search')));
+            $attrs = array(
+                'type' => 'text',
+                'name' => restore_course_search::$VAR_SEARCH,
+                'value' => $component->get_search(),
+                'class' => 'form-control'
+            );
+            $output .= html_writer::empty_tag('input', $attrs);
+            $attrs = array(
+                'type' => 'submit',
+                'name' => 'searchcourses',
+                'value' => get_string('search'),
+                'class' => 'btn btn-secondary'
+            );
+            $output .= html_writer::empty_tag('input', $attrs);
             $output .= html_writer::end_tag('div');
 
             $output .= html_writer::end_tag('div');
@@ -709,8 +739,19 @@ class core_backup_renderer extends plugin_renderer_base {
         $output .= html_writer::end_tag('div');
 
         $output .= html_writer::start_tag('div', array('class' => 'ics-search'));
-        $output .= html_writer::empty_tag('input', array('type' => 'text', 'name' => restore_course_search::$VAR_SEARCH, 'value' => $component->get_search()));
-        $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'searchcourses', 'value' => get_string('search')));
+        $attrs = array(
+            'type' => 'text',
+            'name' => restore_course_search::$VAR_SEARCH,
+            'value' => $component->get_search(),
+            'class' => 'form-control');
+        $output .= html_writer::empty_tag('input', $attrs);
+        $attrs = array(
+            'type' => 'submit',
+            'name' => 'searchcourses',
+            'value' => get_string('search'),
+            'class' => 'btn btn-secondary'
+        );
+        $output .= html_writer::empty_tag('input', $attrs);
         $output .= html_writer::end_tag('div');
 
         $output .= html_writer::end_tag('div');
@@ -726,7 +767,7 @@ class core_backup_renderer extends plugin_renderer_base {
     public function render_restore_category_search(restore_category_search $component) {
         $url = $component->get_url();
 
-        $output = html_writer::start_tag('div', array('class' => 'restore-course-search'));
+        $output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline m-b-1'));
         $output .= html_writer::start_tag('div', array('class' => 'rcs-results'));
 
         $table = new html_table();
@@ -767,8 +808,20 @@ class core_backup_renderer extends plugin_renderer_base {
         $output .= html_writer::end_tag('div');
 
         $output .= html_writer::start_tag('div', array('class' => 'rcs-search'));
-        $output .= html_writer::empty_tag('input', array('type' => 'text', 'name' => restore_category_search::$VAR_SEARCH, 'value' => $component->get_search()));
-        $output .= html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'searchcourses', 'value' => get_string('search')));
+        $attrs = array(
+            'type' => 'text',
+            'name' => restore_category_search::$VAR_SEARCH,
+            'value' => $component->get_search(),
+            'class' => 'form-control'
+        );
+        $output .= html_writer::empty_tag('input', $attrs);
+        $attrs = array(
+            'type' => 'submit',
+            'name' => 'searchcourses',
+            'value' => get_string('search'),
+            'class' => 'btn btn-secondary'
+        );
+        $output .= html_writer::empty_tag('input', $attrs);
         $output .= html_writer::end_tag('div');
 
         $output .= html_writer::end_tag('div');
index 231071a..4c6c808 100644 (file)
@@ -211,7 +211,7 @@ function blog_rss_get_feed($context, $args) {
     if ($blogentries) {
         $items = array();
         foreach ($blogentries as $blogentry) {
-            $item = null;
+            $item = new stdClass();
             $item->author = fullname($DB->get_record('user', array('id' => $blogentry->userid))); // TODO: this is slow.
             $item->title = $blogentry->subject;
             $item->pubdate = $blogentry->lastmodified;
@@ -234,7 +234,7 @@ function blog_rss_get_feed($context, $args) {
 
     switch ($type) {
         case 'user':
-            $info = fullname($DB->get_record('user', array('id' => $id), 'firstname,lastname'));
+            $info = fullname($DB->get_record('user', array('id' => $id), get_all_user_name_fields(true)));
             break;
         case 'course':
             $info = $DB->get_field('course', 'fullname', array('id' => $id));
index 17d8bf7..c7a7825 100644 (file)
@@ -51,7 +51,7 @@ class core_calendar_export_form extends moodleform {
         $export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtogroups', 'calendar'), 'groups');
         $export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventspersonal', 'calendar'), 'user');
 
-        $mform->addGroup($export, 'events', get_string('export', 'calendar'), '<br/>');
+        $mform->addGroup($export, 'events', get_string('eventstoexport', 'calendar'), '<br/>');
         $mform->addGroupRule('events', get_string('required'), 'required');
         $mform->setDefault('events', 'all');
 
@@ -79,7 +79,7 @@ class core_calendar_export_form extends moodleform {
             $range[] = $mform->createElement('radio', 'timeperiod', '', get_string('customexport', 'calendar', $a), 'custom');
         }
 
-        $mform->addGroup($range, 'period', get_string('for', 'calendar'), '<br/>');
+        $mform->addGroup($range, 'period', get_string('timeperiod', 'calendar'), '<br/>');
         $mform->addGroupRule('period', get_string('required'), 'required');
         $mform->setDefault('period', 'recentupcoming');
 
@@ -88,4 +88,4 @@ class core_calendar_export_form extends moodleform {
         $buttons[] = $mform->createElement('submit', 'export', get_string('exportbutton', 'calendar'));
         $mform->addGroup($buttons);
     }
-}
\ No newline at end of file
+}
index 8ebc23e..1e231a8 100644 (file)
     "packages-dev": [
         {
             "name": "behat/behat",
-            "version": "v3.2.1",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Behat.git",
-                "reference": "df7d9225e9ee37fdaa54273e3e0aecf2515bbe76"
+                "reference": "30aa3836825416f28581ee55fcfe6a5b0cdeeb85"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Behat/zipball/df7d9225e9ee37fdaa54273e3e0aecf2515bbe76",
-                "reference": "df7d9225e9ee37fdaa54273e3e0aecf2515bbe76",
+                "url": "https://api.github.com/repos/Behat/Behat/zipball/30aa3836825416f28581ee55fcfe6a5b0cdeeb85",
+                "reference": "30aa3836825416f28581ee55fcfe6a5b0cdeeb85",
                 "shasum": ""
             },
             "require": {
                 "symfony",
                 "testing"
             ],
-            "time": "2016-09-25 09:40:39"
+            "time": "2016-11-05 17:13:53"
         },
         {
             "name": "behat/gherkin",
-            "version": "v4.4.4",
+            "version": "v4.4.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Gherkin.git",
-                "reference": "cf8cc94647101e02a33d690245896d83d880aea1"
+                "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/cf8cc94647101e02a33d690245896d83d880aea1",
-                "reference": "cf8cc94647101e02a33d690245896d83d880aea1",
+                "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74",
+                "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74",
                 "shasum": ""
             },
             "require": {
                 "gherkin",
                 "parser"
             ],
-            "time": "2016-09-18 12:16:14"
+            "time": "2016-10-30 11:50:56"
         },
         {
             "name": "behat/mink",
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "6.2.1",
+            "version": "6.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "3f808fba627f2c5b69e2501217bf31af349c1427"
+                "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/3f808fba627f2c5b69e2501217bf31af349c1427",
-                "reference": "3f808fba627f2c5b69e2501217bf31af349c1427",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60",
+                "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60",
                 "shasum": ""
             },
             "require": {
                 "rest",
                 "web service"
             ],
-            "time": "2016-07-15 17:22:37"
+            "time": "2016-10-08 15:01:37"
         },
         {
             "name": "guzzlehttp/promises",
         },
         {
             "name": "myclabs/deep-copy",
-            "version": "1.5.4",
+            "version": "1.5.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f"
+                "reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f",
-                "reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/399c1f9781e222f6eb6cc238796f5200d1b7f108",
+                "reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108",
                 "shasum": ""
             },
             "require": {
                 "object",
                 "object graph"
             ],
-            "time": "2016-09-16 13:37:59"
+            "time": "2016-10-31 17:19:45"
         },
         {
             "name": "phpdocumentor/reflection-common",
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "4.0.1",
+            "version": "4.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3"
+                "reference": "6cba06ff75a1a63a71033e1a01b89056f3af1e8d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3",
-                "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6cba06ff75a1a63a71033e1a01b89056f3af1e8d",
+                "reference": "6cba06ff75a1a63a71033e1a01b89056f3af1e8d",
                 "shasum": ""
             },
             "require": {
                 "testing",
                 "xunit"
             ],
-            "time": "2016-07-26 14:39:29"
+            "time": "2016-11-01 05:06:24"
         },
         {
             "name": "phpunit/php-file-iterator",
         },
         {
             "name": "phpunit/phpunit-mock-objects",
-            "version": "3.3.1",
+            "version": "3.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "03500345483e1e17b52e2e4d34a89c9408ab2902"
+                "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/03500345483e1e17b52e2e4d34a89c9408ab2902",
-                "reference": "03500345483e1e17b52e2e4d34a89c9408ab2902",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/238d7a2723bce689c79eeac9c7d5e1d623bb9dc2",
+                "reference": "238d7a2723bce689c79eeac9c7d5e1d623bb9dc2",
                 "shasum": ""
             },
             "require": {
                 "mock",
                 "xunit"
             ],
-            "time": "2016-10-04 11:03:26"
+            "time": "2016-10-09 07:01:45"
         },
         {
             "name": "psr/http-message",
         },
         {
             "name": "psr/log",
-            "version": "1.0.1",
+            "version": "1.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "5277094ed527a1c4477177d102fe4c53551953e0"
+                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0",
-                "reference": "5277094ed527a1c4477177d102fe4c53551953e0",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
+                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
                 "shasum": ""
             },
             "require": {
                 "psr",
                 "psr-3"
             ],
-            "time": "2016-09-19 16:02:08"
+            "time": "2016-10-10 12:19:37"
         },
         {
             "name": "sebastian/code-unit-reverse-lookup",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
         },
         {
             "name": "symfony/class-loader",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
         },
         {
             "name": "symfony/config",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
         },
         {
             "name": "symfony/console",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0"
+                "reference": "c99da1119ae61e15de0e4829196b9fba6f73d065"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
-                "reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
+                "url": "https://api.github.com/repos/symfony/console/zipball/c99da1119ae61e15de0e4829196b9fba6f73d065",
+                "reference": "c99da1119ae61e15de0e4829196b9fba6f73d065",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2016-09-28 00:11:12"
+            "time": "2016-10-06 01:44:51"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
         },
         {
             "name": "symfony/debug",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "8fa4be9a36f41e49b0c3969678c41c81ed463816"
+                "reference": "c578891216090069cd6d2e573402e13e39b3ad5c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8fa4be9a36f41e49b0c3969678c41c81ed463816",
-                "reference": "8fa4be9a36f41e49b0c3969678c41c81ed463816",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c578891216090069cd6d2e573402e13e39b3ad5c",
+                "reference": "c578891216090069cd6d2e573402e13e39b3ad5c",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2016-09-24 15:56:48"
+            "time": "2016-10-24 15:52:44"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v3.1.5",
+            "version": "v3.1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "bb7395e8b1db3654de82b9f35d019958276de4d7"
+                "reference": "59eee3c76eb89f21857798620ebdad7a05ad14f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb7395e8b1db3654de82b9f35d019958276de4d7",
-                "reference": "bb7395e8b1db3654de82b9f35d019958276de4d7",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/59eee3c76eb89f21857798620ebdad7a05ad14f4",
+                "reference": "59eee3c76eb89f21857798620ebdad7a05ad14f4",