Merge branch 'wip-MDL-55939-master' of git://github.com/marinaglancy/moodle
authorDavid Monllao <david.monllao@gmail.com>
Mon, 10 Apr 2017 10:57:34 +0000 (12:57 +0200)
committerDavid Monllao <david.monllao@gmail.com>
Mon, 10 Apr 2017 10:57:34 +0000 (12:57 +0200)
33 files changed:
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/thirdpartylibs.xml
cache/stores/redis/addinstanceform.php
cache/stores/redis/lang/en/cachestore_redis.php
cache/stores/redis/lib.php
cache/stores/redis/settings.php
cache/stores/redis/version.php
config-dist.php
files/classes/converter.php
files/converter/unoconv/classes/converter.php
files/tests/converter_test.php
lib/behat/lib.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/session/redis.php
lib/db/services.php
lib/db/upgrade.php
lib/phpunit/bootstrap.php
lib/statslib.php
mod/assign/locallib.php
mod/book/lib.php
mod/feedback/classes/external.php
mod/feedback/classes/external/feedback_completed_exporter.php [new file with mode: 0644]
mod/feedback/db/services.php
mod/feedback/lang/en/deprecated.txt
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/show_nonrespondents.php
mod/feedback/tests/external_test.php
mod/feedback/version.php
version.php

index a86924e..40214ba 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 eaeb288..84a70c2 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 c0fb1d7..a1d9d8c 100644 (file)
-
-/*
-* @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.
-*/
-                    
+/**!
+ * @fileOverview Kickass library to create and place poppers near their reference elements.
+ * @version 1.0.8
+ * @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;
-          }
-      });
-  }
+       typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+       typeof define === 'function' && define.amd ? define(factory) :
+       (global.Popper = factory());
+}(this, (function () { 'use strict';
+
+/**
+ * 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;
+    var nodeName = offsetParent && offsetParent.nodeName;
 
-  /**
-   * 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'];
-      }
+    if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
+        return window.document.documentElement;
+    }
 
-      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;
-          };
-      }
+    return 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 property ? css[property] : css;
+}
+
+/**
+ * Returns the parentNode or the host of the element
+ * @method
+ * @memberof Popper.Utils
+ * @argument {Element} element
+ * @returns {Element} parent
+ */
+function getParentNode(element) {
+    if (element.nodeName === 'HTML') {
+        return element;
+    }
+    return element.parentNode || element.host;
+}
+
+/**
+ * Returns the scrolling parent of the given element
+ * @method
+ * @memberof Popper.Utils
+ * @argument {Element} element
+ * @returns {Element} scroll parent
+ */
+function getScrollParent(element) {
+    // Return body, `getScroll` will take care to get the correct `scrollTop` from it
+    if (!element || ['HTML', 'BODY', '#document'].indexOf(element.nodeName) !== -1) {
+        return window.document.body;
+    }
 
-      if (!window.cancelAnimationFrame) {
-          window.cancelAnimationFrame = function (id) {
-              clearTimeout(id);
-          };
-      }
-  }
+    // Firefox want us to check `-x` and `-y` variations as well
 
-  /**
-   * 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);
-  }
+    var _getStyleComputedProp = getStyleComputedProperty(element),
+        overflow = _getStyleComputedProp.overflow,
+        overflowX = _getStyleComputedProp.overflowX,
+        overflowY = _getStyleComputedProp.overflowY;
 
-  /**
-   * 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;
-  }
+    if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) {
+        return element;
+    }
 
-  /**
-   * 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];
-  }
+    return getScrollParent(getParentNode(element));
+}
+
+/**
+ * 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) {
+    var nodeName = element.nodeName;
+    if (nodeName === 'BODY' || nodeName === 'HTML') {
+        return false;
+    }
+    if (getStyleComputedProperty(element, 'position') === 'fixed') {
+        return true;
+    }
+    return isFixed(getParentNode(element));
+}
+
+/**
+ * Helper used to get the position which will be applied to the popper
+ * @method
+ * @memberof Popper.Utils
+ * @param {HTMLElement} element - popper element
+ * @returns {String} position
+ */
+function getPosition(element) {
+  var container = getOffsetParent(element);
+
+  // 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';
+}
 
-  /**
-   * 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;
-  }
+/*
+ * Helper to detect borders of a given element
+ * @method
+ * @memberof Popper.Utils
+ * @param {CSSStyleDeclaration} styles - result of `getStyleComputedProperty` on the given element
+ * @param {String} axis - `x` or `y`
+ * @return {Number} borders - the borders size of the given axis
+ */
+
+function getBordersSize(styles, axis) {
+  var sideA = axis === 'x' ? 'Left' : 'Top';
+  var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
+
+  return Number(styles['border' + sideA + 'Width'].split('px')[0]) + Number(styles['border' + sideB + 'Width'].split('px')[0]);
+}
+
+/**
+ * Get bounding client rect of given element
+ * @method
+ * @memberof Popper.Utils
+ * @param {HTMLElement} element
+ * @return {Object} client rect
+ */
+function getBoundingClientRect(element) {
+    var isIE10 = navigator.appVersion.indexOf('MSIE 10') !== -1;
+    var rect = void 0;
+
+    // IE10 10 FIX: Please, don't ask, the element isn't
+    // considered in DOM in some circumstances...
+    // This isn't reproducible in IE10 compatibility mode of IE11
+    if (isIE10) {
+        try {
+            rect = element.getBoundingClientRect();
+        } catch (err) {
+            rect = {};
+        }
+    } else {
+        rect = element.getBoundingClientRect();
+    }
 
-  /**
-   * 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;
-          }
-      }
+    var result = {
+        left: rect.left,
+        top: rect.top,
+        right: rect.right,
+        bottom: rect.bottom,
+        width: rect.right - rect.left,
+        height: rect.bottom - rect.top
+    };
 
-      // 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;
-  }
+    // IE10 FIX: `getBoundingClientRect`, when executed on `documentElement`
+    // will not take in account the `scrollTop` and `scrollLeft`
+    if (element.nodeName === 'HTML' && isIE10) {
+        var _window$document$docu = window.document.documentElement,
+            scrollTop = _window$document$docu.scrollTop,
+            scrollLeft = _window$document$docu.scrollLeft;
+
+        result.top -= scrollTop;
+        result.bottom -= scrollTop;
+        result.left -= scrollLeft;
+        result.right -= scrollLeft;
+    }
 
-  /**
-   * 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;
-  }
+    // subtract scrollbar size from sizes
+    var horizScrollbar = rect.width - (element.clientWidth || rect.right - rect.left);
+    var vertScrollbar = rect.height - (element.clientHeight || rect.bottom - rect.top);
 
-  /**
-   * 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;
-  }
+    // if an hypothetical scrollbar is detected, we must be sure it's not a `border`
+    // we make this check conditional for performance reasons
+    if (horizScrollbar || vertScrollbar) {
+        var styles = getStyleComputedProperty(element);
+        horizScrollbar -= getBordersSize(styles, 'x');
+        vertScrollbar -= getBordersSize(styles, 'y');
+    }
 
-  /**
-   * 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
-      };
-  }
+    result.right -= horizScrollbar;
+    result.width -= horizScrollbar;
+    result.bottom -= vertScrollbar;
+    result.height -= vertScrollbar;
 
-  /**
-   * 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;
-      }
+    return result;
+}
 
-      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;
-  }
+function getScroll(element) {
+    var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
 
-  /**
-   * 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;
-  }
+    var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
+    var nodeName = element.nodeName;
 
-  /**
-   * 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
-      });
-  }
+    if (nodeName === 'BODY' || nodeName === 'HTML') {
+        var html = window.document.documentElement;
+        var scrollingElement = window.document.scrollingElement || html;
+        return scrollingElement[upperSide];
+    }
 
-  /**
-   * 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;
-  }
+    return element[upperSide];
+}
 
-  /**
-   * 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';
-  }
+/*
+ * Sum or subtract the element scroll values (left and top) from a given rect object
+ * @method
+ * @memberof Popper.Utils
+ * @param {Object} rect - Rect object you want to change
+ * @param {HTMLElement} element - The element from the function reads the scroll values
+ * @param {Boolean} subtract - set to true if you want to subtract the scroll values
+ * @return {Object} rect - The modifier rect object
+ */
+function includeScroll(rect, element) {
+  var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+
+  var scrollTop = getScroll(element, 'top');
+  var scrollLeft = getScroll(element, 'left');
+  var modifier = subtract ? -1 : 1;
+  rect.top += scrollTop * modifier;
+  rect.bottom += scrollTop * modifier;
+  rect.left += scrollLeft * modifier;
+  rect.right += scrollLeft * modifier;
+  return rect;
+}
+
+/**
+ * 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) {
+    var fixed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+    var transformed = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+
+    var scrollParent = getScrollParent(parent);
+    var elementRect = getBoundingClientRect(element);
+    var parentRect = getBoundingClientRect(parent);
+
+    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
+    };
 
-  /**
-   * 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;
-  }
+    if (fixed && !transformed) {
+        rect = includeScroll(rect, scrollParent, true);
+    }
+    // When a popper doesn't have any positioned or scrollable parents, `offsetParent.contains(scrollParent)`
+    // will return a "false positive". This is happening because `getOffsetParent` returns `html` node,
+    // and `scrollParent` is the `body` node. Hence the additional check.
+    else if (getOffsetParent(element).contains(scrollParent) && scrollParent.nodeName !== 'BODY') {
+            rect = includeScroll(rect, parent);
+        }
+
+    // subtract borderTopWidth and borderTopWidth from final result
+    var styles = getStyleComputedProperty(parent);
+    var borderTopWidth = Number(styles.borderTopWidth.split('px')[0]);
+    var borderLeftWidth = Number(styles.borderLeftWidth.split('px')[0]);
+
+    rect.top -= borderTopWidth;
+    rect.bottom -= borderTopWidth;
+    rect.left -= borderLeftWidth;
+    rect.right -= borderLeftWidth;
+
+    return rect;
+}
+
+function getWindowSizes() {
+    var body = window.document.body;
+    var html = window.document.documentElement;
+    return {
+        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)
+    };
+}
+
+/**
+ * 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 = void 0;
+    if (element.nodeName === 'HTML') {
+        var _getWindowSizes = getWindowSizes(),
+            width = _getWindowSizes.width,
+            height = _getWindowSizes.height;
+
+        elementRect = {
+            width: width,
+            height: height,
+            left: 0,
+            top: 0
+        };
+    } else {
+        elementRect = {
+            width: element.offsetWidth,
+            height: element.offsetHeight,
+            left: element.offsetLeft,
+            top: element.offsetTop
+        };
+    }
 
-  /**
-   * 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]';
-  }
+    elementRect.right = elementRect.left + elementRect.width;
+    elementRect.bottom = elementRect.top + elementRect.height;
+
+    // position
+    return elementRect;
+}
+
+function getOffsetRectRelativeToViewport(element) {
+    // Offset relative to offsetParent
+    var relativeOffset = getOffsetRect(element);
+
+    if (element.nodeName !== 'HTML') {
+        var offsetParent = getOffsetParent(element);
+        var parentOffset = getOffsetRectRelativeToViewport(offsetParent);
+        var offset = {
+            width: relativeOffset.offsetWidth,
+            height: relativeOffset.offsetHeight,
+            left: relativeOffset.left + parentOffset.left,
+            top: relativeOffset.top + parentOffset.top,
+            right: relativeOffset.right - parentOffset.right,
+            bottom: relativeOffset.bottom - parentOffset.bottom
+        };
+        return offset;
+    }
 
-  /**
-   * 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;
-  }
+    return relativeOffset;
+}
 
-  /**
-   * 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);
-  }
+function getTotalScroll(element) {
+    var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top';
 
-  /**
-   * 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;
-  }
+    var scrollParent = getScrollParent(element);
+    var scroll = getScroll(scrollParent, side);
 
-  /**
-   * 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;
-  }
+    if (['BODY', 'HTML'].indexOf(scrollParent.nodeName) === -1) {
+        return scroll + getTotalScroll(getParentNode(scrollParent), side);
+    }
+    return scroll;
+}
+
+/**
+ * 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, padding, boundariesElement) {
+    // NOTE: 1 DOM access here
+    var boundaries = { top: 0, left: 0 };
+    var offsetParent = getOffsetParent(popper);
+
+    // Handle viewport case
+    if (boundariesElement === 'viewport') {
+        var _getOffsetRectRelativ = getOffsetRectRelativeToViewport(offsetParent),
+            left = _getOffsetRectRelativ.left,
+            top = _getOffsetRectRelativ.top;
+
+        var _window$document$docu = window.document.documentElement,
+            width = _window$document$docu.clientWidth,
+            height = _window$document$docu.clientHeight;
+
+
+        if (getPosition(popper) === 'fixed') {
+            boundaries.right = width;
+            boundaries.bottom = height;
+        } else {
+            var scrollLeft = getTotalScroll(popper, 'left');
+            var scrollTop = getTotalScroll(popper, 'top');
+
+            boundaries = {
+                top: 0 - top,
+                right: width - left + scrollLeft,
+                bottom: height - top + scrollTop,
+                left: 0 - left
+            };
+        }
+    }
+    // Handle other cases based on DOM element used as boundaries
+    else {
+            var boundariesNode = void 0;
+            if (boundariesElement === 'scrollParent') {
+                boundariesNode = getScrollParent(getParentNode(popper));
+            } else if (boundariesElement === 'window') {
+                boundariesNode = window.document.body;
+            } else {
+                boundariesNode = boundariesElement;
+            }
+
+            // In case of BODY, we need a different computation
+            if (boundariesNode.nodeName === 'BODY') {
+                var _getWindowSizes = getWindowSizes(),
+                    _height = _getWindowSizes.height,
+                    _width = _getWindowSizes.width;
+
+                boundaries.right = _width;
+                boundaries.bottom = _height;
+            }
+            // for all the other DOM elements, this one is good
+            else {
+                    boundaries = getOffsetRectRelativeToCustomParent(boundariesNode, offsetParent, isFixed(popper));
+                }
+        }
+
+    // Add paddings
+    boundaries.left += padding;
+    boundaries.top += padding;
+    boundaries.right -= padding;
+    boundaries.bottom -= padding;
+
+    return boundaries;
+}
+
+/**
+ * Utility used to transform the `auto` placement to the placement with more
+ * available space.
+ * @method
+ * @memberof Popper.Utils
+ * @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 computeAutoPlacement(placement, refRect, popper) {
+    if (placement.indexOf('auto') === -1) {
+        return placement;
+    }
 
-  /**
-   * 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;
-      });
-  }
+    var boundaries = getBoundaries(popper, 0, 'scrollParent');
 
-  /** @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
-  };
+    var sides = {
+        top: refRect.top - boundaries.top,
+        right: boundaries.right - refRect.right,
+        bottom: boundaries.bottom - refRect.bottom,
+        left: refRect.left - boundaries.left
+    };
 
-  /**
-   * 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;
-          }
-      }
+    var computedPlacement = Object.keys(sides).sort(function (a, b) {
+        return sides[b] - sides[a];
+    })[0];
+    var variation = placement.split('-')[1];
+
+    return computedPlacement + (variation ? '-' + variation : '');
+}
+
+var nativeHints = ['native code', '[object MutationObserverConstructor]' // for mobile safari iOS 9.0
+];
+
+/**
+ * Determine if a function is implemented natively (as opposed to a polyfill).
+ * @argument {Function | undefined} fn the function to check
+ * @returns {boolean}
+ */
+var isNative = (function (fn) {
+  return nativeHints.some(function (hint) {
+    return (fn || '').toString().indexOf(hint) > -1;
+  });
+});
+
+var isBrowser = typeof window !== 'undefined';
+var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
+var timeoutDuration = 0;
+for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) {
+    if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
+        timeoutDuration = 1;
+        break;
+    }
+}
+
+function microtaskDebounce(fn) {
+    var scheduled = false;
+    var i = 0;
+    var elem = document.createElement('span');
+
+    // MutationObserver provides a mechanism for scheduling microtasks, which
+    // are scheduled *before* the next task. This gives us a way to debounce
+    // a function but ensure it's called *before* the next paint.
+    var observer = new MutationObserver(function () {
+        fn();
+        scheduled = false;
+    });
 
-      // Add width and height to our offsets object
-      popperOffsets.width = popperRect.width;
-      popperOffsets.height = popperRect.height;
+    observer.observe(elem, { attributes: true });
 
-      return {
-          popper: popperOffsets,
-          reference: referenceOffsets
-      };
-  }
+    return function () {
+        if (!scheduled) {
+            scheduled = true;
+            elem.setAttribute('x-index', i);
+            i = i + 1; // don't use compund (+=) because it doesn't get optimized in V8
+        }
+    };
+}
+
+function taskDebounce(fn) {
+    var scheduled = false;
+    return function () {
+        if (!scheduled) {
+            scheduled = true;
+            setTimeout(function () {
+                scheduled = false;
+                fn();
+            }, timeoutDuration);
+        }
+    };
+}
 
-  /**
-   * 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 });
-      }
-  }
+// It's common for MutationObserver polyfills to be seen in the wild, however
+// these rely on Mutation Events which only occur when an element is connected
+// to the DOM. The algorithm used in this module does not use a connected element,
+// and so we must ensure that a *native* MutationObserver is available.
+var supportsNativeMutationObserver = isBrowser && isNative(window.MutationObserver);
 
-  /**
-   * 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;
-  }
+/**
+* Create a debounced version of a method, that's asynchronously deferred
+* but called in the minimum time possible.
+*
+* @method
+* @memberof Popper.Utils
+* @argument {Function} fn
+* @returns {Function}
+*/
+var debounce = supportsNativeMutationObserver ? microtaskDebounce : taskDebounce;
+
+/**
+ * Mimics the `find` method of Array
+ * @method
+ * @memberof Popper.Utils
+ * @argument {Array} arr
+ * @argument prop
+ * @argument value
+ * @returns index or -1
+ */
+function find(arr, check) {
+    // use native find if supported
+    if (Array.prototype.find) {
+        return arr.find(check);
+    }
 
-  /**
-   * 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;
-  }
+    // use `filter` to obtain the same behavior of `find`
+    return arr.filter(check)[0];
+}
+
+/**
+ * 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 native findIndex if supported
+    if (Array.prototype.findIndex) {
+        return arr.findIndex(function (cur) {
+            return cur[prop] === value;
+        });
+    }
 
-  /**
-   * 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);
-      }
+    // use `find` + `indexOf` if `findIndex` isn't supported
+    var match = find(arr, function (obj) {
+        return obj[prop] === value;
+    });
+    return arr.indexOf(match);
+}
 
-      return data;
+var classCallCheck = function (instance, Constructor) {
+  if (!(instance instanceof Constructor)) {
+    throw new TypeError("Cannot call a class as a function");
   }
-
-  /**
-   * 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);
+};
+
+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);
+    }
   }
 
-  /**
-   * 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);
-      }
+  return function (Constructor, protoProps, staticProps) {
+    if (protoProps) defineProperties(Constructor.prototype, protoProps);
+    if (staticProps) defineProperties(Constructor, staticProps);
+    return Constructor;
+  };
+}();
 
-      // 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 (!data.instance.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 (!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];
+
+var defineProperty = function (obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true
+    });
+  } else {
+    obj[key] = value;
+  }
+
+  return obj;
+};
+
+var _extends = Object.assign || function (target) {
+  for (var i = 1; i < arguments.length; i++) {
+    var source = arguments[i];
+
+    for (var key in source) {
+      if (Object.prototype.hasOwnProperty.call(source, key)) {
+        target[key] = source[key];
       }
+    }
+  }
 
-      // compute center of the popper
-      var center = reference[side] + reference[len] / 2 - arrowSize / 2;
+  return target;
+};
+
+/**
+ * Given the popper offsets, generate an output similar to getBoundingClientRect
+ * @method
+ * @memberof Popper.Utils
+ * @argument {Object} popperOffsets
+ * @returns {Object} ClientRect like output
+ */
+function getClientRect(popperOffsets) {
+    return _extends({}, popperOffsets, {
+        right: popperOffsets.left + popperOffsets.width,
+        bottom: popperOffsets.top + popperOffsets.height
+    });
+}
+
+/**
+ * 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) {
+    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
+    };
+    return result;
+}
+
+/**
+ * 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 offsets to the popper
+ * @method
+ * @memberof Popper.Utils
+ * @param {Object} position - CSS position the Popper will get applied
+ * @param {HTMLElement} popper - the popper element
+ * @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
+ * @param {String} placement - one of the valid placement options
+ * @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
+ */
+function getPopperOffsets(position, popper, referenceOffsets, placement) {
+    placement = placement.split('-')[0];
+
+    // Get popper node sizes
+    var popperRect = getOuterSizes(popper);
+
+    // Add position, width and height to our offsets object
+    var popperOffsets = {
+        position: position,
+        width: popperRect.width,
+        height: popperRect.height
+    };
 
-      // Compute the sideValue using the updated popper offsets
-      var sideValue = center - getPopperClientRect(data.offsets.popper)[side];
+    // depending by the popper placement we have to compute its offsets slightly differently
+    var isHoriz = ['right', 'left'].indexOf(placement) !== -1;
+    var mainSide = isHoriz ? 'top' : 'left';
+    var secondarySide = isHoriz ? 'left' : 'top';
+    var measurement = isHoriz ? 'height' : 'width';
+    var secondaryMeasurement = !isHoriz ? 'height' : 'width';
+
+    popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
+    if (placement === secondarySide) {
+        popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
+    } else {
+        popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
+    }
 
-      // 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
+    return popperOffsets;
+}
+
+/**
+ * Get offsets to the reference element
+ * @method
+ * @memberof Popper.Utils
+ * @param {Object} state
+ * @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 getReferenceOffsets(state, popper, reference) {
+  var isParentFixed = state.position === 'fixed';
+  var isParentTransformed = state.isParentTransformed;
+  var offsetParent = getOffsetParent(isParentFixed && isParentTransformed ? reference : popper);
+
+  return getOffsetRectRelativeToCustomParent(reference, offsetParent, isParentFixed, isParentTransformed);
+}
+
+/**
+ * Get the prefixed supported property name
+ * @method
+ * @memberof Popper.Utils
+ * @argument {String} property (camelCase)
+ * @returns {String} prefixed property (camelCase)
+ */
+function getSupportedPropertyName(property) {
+    var prefixes = [false, 'ms', 'webkit', 'moz', 'o'];
+    var upperProp = property.charAt(0).toUpperCase() + property.slice(1);
+
+    for (var i = 0; i < prefixes.length - 1; i++) {
+        var prefix = prefixes[i];
+        var toCheck = prefix ? '' + prefix + upperProp : 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 {*} functionToCheck - variable 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 is enabled.
+ * @method
+ * @memberof Popper.Utils
+ * @returns {Boolean}
+ */
+function isModifierEnabled(modifiers, modifierName) {
+    return modifiers.some(function (_ref) {
+        var name = _ref.name,
+            enabled = _ref.enabled;
+        return enabled && name === modifierName;
+    });
+}
+
+/**
+ * Helper used to know if the given modifier depends from another one.
+ * It checks if the needed modifier is listed and enabled.
+ * @method
+ * @memberof Popper.Utils
+ * @param {Array} modifiers - list of modifiers
+ * @param {String} requestingName - name of requesting modifier
+ * @param {String} requestedName - name of requested modifier
+ * @returns {Boolean}
+ */
+function isModifierRequired(modifiers, requestingName, requestedName) {
+    var requesting = find(modifiers, function (_ref) {
+        var name = _ref.name;
+        return name === requestingName;
+    });
 
-      data.offsets.arrow = arrowStyle;
-      data.arrowElement = arrow;
+    return !!requesting && modifiers.some(function (modifier) {
+        return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
+    });
+}
+
+/**
+ * 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.nodeName === 'BODY') {
+        return false;
+    }
+    if (getStyleComputedProperty(element, 'transform') !== 'none') {
+        return true;
+    }
+    return getParentNode(element) ? isTransformed(getParentNode(element)) : element;
+}
+
+/**
+ * Remove event listeners used to update the popper position
+ * @method
+ * @memberof Popper.Utils
+ * @private
+ */
+function removeEventListeners(reference, state) {
+    // Remove resize event listener on window
+    window.removeEventListener('resize', state.updateBound);
+
+    // Remove scroll event listener on scroll parents
+    state.scrollParents.forEach(function (target) {
+        target.removeEventListener('scroll', state.updateBound);
+    });
 
-      return data;
-  }
+    // Reset state
+    state.updateBound = null;
+    state.scrollParents = [];
+    state.scrollElement = null;
+    state.eventsEnabled = false;
+    return state;
+}
+
+/**
+ * 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, 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);
+        }
+    });
 
-  /**
-   * 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];
+    return data;
+}
+
+/**
+ * Set the attributes to the given popper
+ * @method
+ * @memberof Popper.Utils
+ * @argument {Element} element - Element to apply the attributes to
+ * @argument {Object} styles - Object with a list of properties and values which will be applied to the element
+ */
+function setAttributes(element, attributes) {
+    Object.keys(attributes).forEach(function (prop) {
+        var value = attributes[prop];
+        if (value !== false) {
+            element.setAttribute(prop, attributes[prop]);
+        } else {
+            element.removeAttribute(prop);
+        }
     });
-  }
+}
+
+/**
+ * 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 setStyles(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;
+    });
+}
 
-  /**
-   * 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;
-  }
+function attachToScrollParents(scrollParent, event, callback, scrollParents) {
+    var isBody = scrollParent.nodeName === 'BODY';
+    var target = isBody ? window : scrollParent;
+    target.addEventListener(event, callback, { passive: true });
 
-  /**
-   * 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;
-      }
+    if (!isBody) {
+        attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
+    }
+    scrollParents.push(target);
+}
+
+/**
+ * Setup needed event listeners used to update the popper position
+ * @method
+ * @memberof Popper.Utils
+ * @private
+ */
+function setupEventListeners(reference, options, state, updateBound) {
+    // Resize event listener on window
+    state.updateBound = updateBound;
+    window.addEventListener('resize', state.updateBound, { passive: true });
+
+    // Scroll event listener on scroll parents
+    var scrollElement = getScrollParent(reference);
+    attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
+    state.scrollElement = scrollElement;
+    state.eventsEnabled = true;
+
+    return state;
+}
+
+/** @namespace Popper.Utils */
+var Utils = {
+    computeAutoPlacement: computeAutoPlacement,
+    debounce: debounce,
+    findIndex: findIndex,
+    getBordersSize: getBordersSize,
+    getBoundaries: getBoundaries,
+    getBoundingClientRect: getBoundingClientRect,
+    getClientRect: getClientRect,
+    getOffsetParent: getOffsetParent,
+    getOffsetRect: getOffsetRect,
+    getOffsetRectRelativeToCustomParent: getOffsetRectRelativeToCustomParent,
+    getOuterSizes: getOuterSizes,
+    getParentNode: getParentNode,
+    getPopperOffsets: getPopperOffsets,
+    getPosition: getPosition,
+    getReferenceOffsets: getReferenceOffsets,
+    getScroll: getScroll,
+    getScrollParent: getScrollParent,
+    getStyleComputedProperty: getStyleComputedProperty,
+    getSupportedPropertyName: getSupportedPropertyName,
+    getTotalScroll: getTotalScroll,
+    getWindowSizes: getWindowSizes,
+    includeScroll: includeScroll,
+    isFixed: isFixed,
+    isFunction: isFunction,
+    isModifierEnabled: isModifierEnabled,
+    isModifierRequired: isModifierRequired,
+    isNative: isNative,
+    isNumeric: isNumeric,
+    isTransformed: isTransformed,
+    removeEventListeners: removeEventListeners,
+    runModifiers: runModifiers,
+    setAttributes: setAttributes,
+    setStyles: setStyles,
+    setupEventListeners: setupEventListeners
+};
+
+/**
+ * Apply the computed styles to the popper element
+ * @method
+ * @memberof Modifiers
+ * @argument {Object} data - The data object generated by `update` method
+ * @argument {Object} data.styles - List of style properties - values to apply to popper element
+ * @argument {Object} data.attributes - List of attribute properties - values to apply to popper element
+ * @argument {Object} options - Modifiers configuration and options
+ * @returns {Object} The same data object
+ */
+function applyStyle(data, options) {
+    // apply the final offsets to the popper
+    // NOTE: 1 DOM access here
+    var styles = {
+        position: data.offsets.popper.position
+    };
 
-      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 attributes = {
+        'x-placement': data.placement
+    };
 
-      var placement = data.placement.split('-')[0];
-      var placementOpposite = getOppositePlacement(placement);
-      var variation = data.placement.split('-')[1] || '';
+    // 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 (options.gpuAcceleration && prefixedProperty) {
+        styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
+        styles.top = 0;
+        styles.left = 0;
+        styles.willChange = 'transform';
+    }
+    // othwerise, we use the standard `left` and `top` properties
+    else {
+            styles.left = left;
+            styles.top = top;
+            styles.willChange = 'top, left';
+        }
+
+    // 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!
+    setStyles(data.instance.popper, _extends({}, styles, data.styles));
+
+    // any property present in `data.attributes` will be applied to the popper,
+    // they will be set as HTML attributes of the element
+    setAttributes(data.instance.popper, _extends({}, attributes, data.attributes));
+
+    // if the arrow style has been computed, apply the arrow style
+    if (data.offsets.arrow) {
+        setStyles(data.arrowElement, data.offsets.arrow);
+    }
 
-      var flipOrder = [];
+    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, modifierOptions, state) {
+    // compute reference element offsets
+    var referenceOffsets = getReferenceOffsets(state, popper, reference);
+
+    // compute auto placement, store placement inside the data object,
+    // modifiers will be able to edit `placement` if needed
+    // and refer to originalPlacement to know the original value
+    options.placement = computeAutoPlacement(options.placement, referenceOffsets, popper);
+
+    popper.setAttribute('x-placement', options.placement);
+    return options;
+}
+
+/**
+ * Modifier used to move the arrowElements 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 arrowElement 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) {
+    // 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;
+    }
 
-      if (options.behavior === 'flip') {
-          flipOrder = [placement, placementOpposite];
-      } else {
-          flipOrder = options.behavior;
-      }
+    var arrowElement = options.element;
+
+    // if arrowElement is a string, suppose it's a CSS selector
+    if (typeof arrowElement === 'string') {
+        arrowElement = data.instance.popper.querySelector(arrowElement);
+
+        // if arrowElement is not found, don't run the modifier
+        if (!arrowElement) {
+            return data;
+        }
+    } else {
+        // if the arrowElement isn't a query selector we must check that the
+        // provided DOM node is child of its popper node
+        if (!data.instance.popper.contains(arrowElement)) {
+            console.warn('WARNING: `arrow.element` must be child of its popper element!');
+            return data;
+        }
+    }
 
-      flipOrder.forEach(function (step, index) {
-          if (placement !== step || flipOrder.length === index + 1) {
-              return data;
-          }
+    var placement = data.placement.split('-')[0];
+    var popper = getClientRect(data.offsets.popper);
+    var reference = data.offsets.reference;
+    var isVertical = ['left', 'right'].indexOf(placement) !== -1;
 
-          placement = data.placement.split('-')[0];
-          placementOpposite = getOppositePlacement(placement);
+    var len = isVertical ? 'height' : 'width';
+    var side = isVertical ? 'top' : 'left';
+    var altSide = isVertical ? 'left' : 'top';
+    var opSide = isVertical ? 'bottom' : 'right';
+    var arrowElementSize = getOuterSizes(arrowElement)[len];
 
-          var popperOffsets = getPopperClientRect(data.offsets.popper);
+    //
+    // extends keepTogether behavior making sure the popper and its reference have enough pixels in conjuction
+    //
 
-          // 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;
+    // top/left side
+    if (reference[opSide] - arrowElementSize < popper[side]) {
+        data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize);
+    }
+    // bottom/right side
+    if (reference[side] + arrowElementSize > popper[opSide]) {
+        data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide];
+    }
 
-          // 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]);
+    // compute center of the popper
+    var center = reference[side] + reference[len] / 2 - arrowElementSize / 2;
+
+    // Compute the sideValue using the updated popper offsets
+    var sideValue = center - getClientRect(data.offsets.popper)[side];
+
+    // prevent arrowElement from being placed not contiguously to its popper
+    sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0);
+
+    data.arrowElement = arrowElement;
+    data.offsets.arrow = {};
+    data.offsets.arrow[side] = sideValue;
+    data.offsets.arrow[altSide] = ''; // make sure to unset any eventual altSide value from the DOM node
+
+    return data;
+}
+
+/**
+ * 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) {
+    // if `inner` modifier is enabled, we can't use the `flip` modifier
+    if (isModifierEnabled(data.instance.modifiers, 'inner')) {
+        return data;
+    }
 
-          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 (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;
+    }
 
-          if (flippedPosition || flippedVariation) {
-              // this boolean to detect any flip loop
-              data.flipped = true;
+    var boundaries = getBoundaries(data.instance.popper, options.padding, options.boundariesElement);
 
-              if (flippedPosition) {
-                  placement = flipOrder[index + 1];
-              }
-              if (flippedVariation) {
-                  variation = getOppositeVariation(variation);
-              }
+    var placement = data.placement.split('-')[0];
+    var placementOpposite = getOppositePlacement(placement);
+    var variation = data.placement.split('-')[1] || '';
 
-              data.placement = placement + (variation ? '-' + variation : '');
-              data.offsets.popper = getOffsets(data.instance.state, data.instance.popper, data.instance.reference, data.placement).popper;
+    var flipOrder = [];
 
-              data = runModifiers(data.instance.modifiers, data.instance.options, data, 'flip');
-          }
-      });
-      return data;
-  }
+    if (options.behavior === 'flip') {
+        flipOrder = [placement, placementOpposite];
+    } else {
+        flipOrder = options.behavior;
+    }
 
-  /**
-   * 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);
-          }
-      }
+    flipOrder.forEach(function (step, index) {
+        if (placement !== step || flipOrder.length === index + 1) {
+            return data;
+        }
 
-      return data;
-  }
+        placement = data.placement.split('-')[0];
+        placementOpposite = getOppositePlacement(placement);
 
-  /**
-   * 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;
-                              }
-          });
-      }
+        var popperOffsets = getClientRect(data.offsets.popper);
+        var refOffsets = data.offsets.reference;
 
-      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;
-  }
+        // using floor because the reference offsets may contain decimals we are not going to consider here
+        var floor = Math.floor;
+        var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom);
 
-  /**
-   * 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;
-          }
-
-          return false;
-      }
-      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;
-  }
+        var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left);
+        var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right);
+        var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top);
+        var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom);
 
-  /**
-   * 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]);
-      }
+        var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom;
 
-      return data;
-  }
+        // flip the variation if required
+        var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
+        var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom);
 
-  /**
-   * 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');
-      }
+        if (overlapsRef || overflowsBoundaries || flippedVariation) {
+            // this boolean to detect any flip loop
+            data.flipped = true;
 
-      return data;
-  }
+            if (overlapsRef || overflowsBoundaries) {
+                placement = flipOrder[index + 1];
+            }
 
-  /**
-   * 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
-  };
+            if (flippedVariation) {
+                variation = getOppositeVariation(variation);
+            }
 
-  var modifiersOnLoad = {
-    applyStyleOnLoad: applyStyleOnLoad
-  };
+            data.placement = placement + (variation ? '-' + variation : '');
+            data.offsets.popper = getPopperOffsets(data.instance.state.position, data.instance.popper, data.offsets.reference, data.placement);
 
-  /**
-   * 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");
+            data = runModifiers(data.instance.modifiers, 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 = getClientRect(data.offsets.popper);
+    var reference = data.offsets.reference;
+    var placement = data.placement.split('-')[0];
+    var floor = Math.floor;
+    var isVertical = ['top', 'bottom'].indexOf(placement) !== -1;
+    var side = isVertical ? 'right' : 'bottom';
+    var opSide = isVertical ? 'left' : 'top';
+    var measurement = isVertical ? 'width' : 'height';
+
+    if (popper[side] < floor(reference[opSide])) {
+        data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement];
+    }
+    if (popper[opSide] > floor(reference[side])) {
+        data.offsets.popper[opSide] = floor(reference[side]);
     }
-  };
 
-  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);
-      }
+    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 width
+            // 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;
+            }
+
+            var measurement = useHeight ? 'height' : 'width';
+
+            // if is a percentage relative to the popper (%p), we calculate the value of it using
+            // as base the sizes of the popper
+            // if is a percentage (% or %r), we calculate the value of it using as base the
+            // sizes of the reference element
+            if (unit.indexOf('%') === 0) {
+                var element = void 0;
+                switch (unit) {
+                    case '%p':
+                        element = data.offsets.popper;
+                        break;
+                    case '%':
+                    case '$r':
+                    default:
+                        element = data.offsets.reference;
+                }
+
+                var rect = getClientRect(element);
+                var len = rect[measurement];
+                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;
+                        }
+        });
     }
 
-    return function (Constructor, protoProps, staticProps) {
-      if (protoProps) defineProperties(Constructor.prototype, protoProps);
-      if (staticProps) defineProperties(Constructor, staticProps);
-      return Constructor;
+    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 prevent the popper from being positioned outside the boundary.
+ *
+ * An scenario exists where the reference itself is not within the boundaries. We can
+ * say it has "escaped the boundaries" — or just "escaped". In this case we need to
+ * decide whether the popper should either:
+ *
+ * - detach from the reference and remain "trapped" in the boundaries, or
+ * - if it should be ignore the boundary and "escape with the reference"
+ *
+ * When `escapeWithReference` is `true`, and reference is completely outside the
+ * boundaries, the popper will overflow (or completely leave) the boundaries in order
+ * to remain attached to the edge of the reference.
+ *
+ * @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) {
+    var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper);
+    var boundaries = getBoundaries(data.instance.popper, options.padding, boundariesElement);
+    options.boundaries = boundaries;
+
+    var order = options.priority;
+    var popper = getClientRect(data.offsets.popper);
+
+    var check = {
+        primary: function primary(placement) {
+            var value = popper[placement];
+            if (popper[placement] < boundaries[placement] && !options.escapeWithReference) {
+                value = Math.max(popper[placement], boundaries[placement]);
+            }
+            return defineProperty({}, placement, value);
+        },
+        secondary: function secondary(placement) {
+            var mainSide = placement === 'right' ? 'left' : 'top';
+            var value = popper[mainSide];
+            if (popper[placement] > boundaries[placement] && !options.escapeWithReference) {
+                value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height));
+            }
+            return defineProperty({}, mainSide, value);
+        }
     };
-  }();
-
-  // 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;
-      }
+    order.forEach(function (placement) {
+        var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary';
+        popper = _extends({}, popper, check[side](placement));
+    });
+
+    data.offsets.popper = popper;
+
+    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 = getClientRect(data.offsets.popper);
+        var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1;
+        var side = isVertical ? 'left' : 'top';
+        var measurement = isVertical ? 'width' : 'height';
+
+        var shiftOffsets = {
+            start: defineProperty({}, side, reference[side]),
+            end: defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement])
+        };
+
+        data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]);
+    }
+
+    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) {
+    if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) {
+        console.warn('WARNING: preventOverflow modifier is required by hide modifier in order to work, be sure to include it before hide!');
+        return data;
+    }
+
+    var refRect = data.offsets.reference;
+    var bound = find(data.instance.modifiers, function (modifier) {
+        return modifier.name === 'preventOverflow';
+    }).boundaries;
+
+    if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) {
+        // Avoid unnecessary DOM access if visibility hasn't changed
+        if (data.hide === true) {
+            return data;
+        }
+
+        data.hide = true;
+        data.attributes['x-out-of-boundaries'] = '';
+    } else {
+        // Avoid unnecessary DOM access if visibility hasn't changed
+        if (data.hide === false) {
+            return data;
+        }
+
+        data.hide = false;
+        data.attributes['x-out-of-boundaries'] = false;
+    }
+
+    return data;
+}
+
+/**
+ * Modifier used to make the popper flow toward the inner of the reference element.
+ * By default, when this modifier is disabled, the popper will be placed outside
+ * the 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 inner(data) {
+    var placement = data.placement;
+    var basePlacement = placement.split('-')[0];
+    var popper = getClientRect(data.offsets.popper);
+    var reference = getClientRect(data.offsets.reference);
+    var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1;
+
+    var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1;
+
+    popper[isHoriz ? 'left' : 'top'] = reference[placement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0);
+
+    data.placement = getOppositePlacement(placement);
+    data.offsets.popper = getClientRect(popper);
+
+    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 modifiers = {
+  shift: {
+    order: 100,
+    enabled: true,
+    function: shift
+  },
+  offset: {
+    order: 200,
+    enabled: true,
+    function: offset,
+    // nudges popper from its origin by the given amount of pixels (can be negative)
+    offset: 0
+  },
+  preventOverflow: {
+    order: 300,
+    enabled: true,
+    function: 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'],
+    // amount of pixel used to define a minimum distance between the boundaries and the popper
+    // this makes sure the popper has always a little padding between the edges of its container
+    padding: 5,
+    boundariesElement: 'scrollParent'
+  },
+  keepTogether: {
+    order: 400,
+    enabled: true,
+    function: keepTogether
+  },
+  arrow: {
+    order: 500,
+    enabled: true,
+    function: arrow,
+    // selector or node used as arrow
+    element: '[x-arrow]'
+  },
+  flip: {
+    order: 600,
+    enabled: true,
+    function: flip,
+    // the behavior used to change the popper's placement
+    behavior: 'flip',
+    // the popper will flip if it hits the edges of the boundariesElement - padding
+    padding: 5,
+    boundariesElement: 'viewport'
+  },
+  inner: {
+    order: 700,
+    enabled: false,
+    function: inner
+  },
+  hide: {
+    order: 800,
+    enabled: true,
+    function: hide
+  },
+  applyStyle: {
+    order: 900,
+    enabled: true,
+    // if true, it uses the CSS 3d transformation to position the popper
+    gpuAcceleration: true,
+    function: applyStyle,
+    onLoad: 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
+ */
+
+// Utils
+// Modifiers
+// default options
+var DEFAULTS = {
+    // placement of the popper
+    placement: 'bottom',
+
+    // whether events (resize, scroll) are initially enabled
+    eventsEnabled: true,
+
+    /**
+     * Callback called when the popper is created.
+     * By default, is set to no-op.
+     * Access Popper.js instance with `data.instance`.
+     * @callback createCallback
+     * @static
+     * @param {dataObject} data
+     */
+    onCreate: function onCreate() {},
+
+    /**
+     * Callback called when the popper is updated, this callback is not called
+     * on the initialization/creation of the popper, but only on subsequent
+     * updates.
+     * By default, is set to no-op.
+     * Access Popper.js instance with `data.instance`.
+     * @callback updateCallback
+     * @static
+     * @param {dataObject} data
+     */
+    onUpdate: function onUpdate() {},
+
+    // list of functions used to modify the offsets before they are applied to the popper
+    modifiers: modifiers
+};
+
+/**
+ * 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, -end),
+ *      left(-start, -end)`
+ *
+ * @param {Boolean} options.eventsEnabled=true
+ *      Whether events (resize, scroll) are initially enabled
+ * @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 {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 {String|HTMLElement} 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 {String|HTMLElement} options.modifiers.preventOverflow.boundariesElement='scrollParent'
+ *      Boundaries used by the modifier, can be `scrollParent`, `window`, `viewport` or any DOM element.
+ * @param {Number} options.modifiers.preventOverflow.padding=5
+ *      Amount of pixel used to define a minimum distance between the boundaries and the popper
+ *      this makes sure the popper has always a little padding between the edges of its container.
+ *
+ * @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)
+ * @param {String|HTMLElement} options.modifiers.flip.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 {Object} options.modifiers.inner - Inner modifier configuration
+ * @param {Number} options.modifiers.inner.enabled=false
+ *      Set to `true` to make the popper flow toward the inner of the reference element.
+ *
+ * @param {Number} options.modifiers.flip.padding=5
+ *      Amount of pixel used to define a minimum distance between the boundaries and the popper
+ *      this makes sure the popper has always a little padding between the edges of its container.
+ *
+ * @param {createCallback} options.onCreate - onCreate callback
+ *      Function called after the Popper has been instantiated.
+ *
+ * @param {updateCallback} options.onUpdate - onUpdate callback
+ *      Function called on subsequent updates of Popper.
+ *
+ * @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.scheduleUpdate = function () {
+            return requestAnimationFrame(_this.update);
+        };
+
+        // make update() debounced, so that it only runs at most once-per-tick
+        this.update = debounce(this.update.bind(this));
+
+        // with {} we create a new object with the options inside it
+        this.options = _extends({}, Popper.Defaults, options);
+
+        // init state
+        this.state = {
+            isDestroyed: false,
+            isCreated: false,
+            scrollParents: []
+        };
+
+        // get reference and popper elements (allow jQuery wrappers)
+        this.reference = reference.jquery ? reference[0] : reference;
+        this.popper = popper.jquery ? popper[0] : popper;
+
+        // refactoring modifiers' list (Object => Array)
+        this.modifiers = Object.keys(Popper.Defaults.modifiers).map(function (name) {
+            return _extends({ name: name }, Popper.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] || {};
+            return _extends({}, defaultConfig, userConfig);
+        });
+
+        // add custom modifiers to the modifiers list
+        if (options.modifiers) {
+            this.options.modifiers = _extends({}, Popper.Defaults.modifiers, options.modifiers);
+            Object.keys(options.modifiers).forEach(function (name) {
+                // take in account only custom modifiers
+                if (Popper.Defaults.modifiers[name] === undefined) {
+                    var modifier = options.modifiers[name];
+                    modifier.name = name;
+                    _this.modifiers.push(modifier);
+                }
+            });
+        }
+
+        // get the popper position type
+        this.state.position = getPosition(this.reference);
+
+        // sort the modifiers by order
+        this.modifiers = this.modifiers.sort(function (a, b) {
+            return a.order - b.order;
+        });
+
+        // modifiers have the ability to execute arbitrary code when Popper.js get inited
+        // such code is executed in the same order of its modifier
+        // they could add new properties to their options configuration
+        // BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`!
+        this.modifiers.forEach(function (modifierOptions) {
+            if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) {
+                modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state);
+            }
+        });
+
+        // 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();
+
+        var eventsEnabled = this.options.eventsEnabled;
+        if (eventsEnabled) {
+            // setup event listeners, they will take care of update the position in specific situations
+            this.enableEventListeners();
+        }
+
+        this.state.eventsEnabled = eventsEnabled;
+    }
 
-      //
-      // 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
+    //
+    // Methods
+    //
+
+    /**
+     * Updates the position of the popper, computing the new offsets and applying the new style
+     * Prefer `scheduleUpdate` over `update` because of performance reasons
+     * @method
+     * @memberof Popper
+     */
+
+
+    createClass(Popper, [{
+        key: 'update',
+        value: function update() {
+            // if popper is destroyed, don't perform any further update
+            if (this.state.isDestroyed) {
+                return;
+            }
+
+            var data = {
+                instance: this,
+                styles: {},
+                attributes: {},
+                flipped: false,
+                offsets: {}
+            };
+
+            // make sure to apply the popper position before any computation
+            this.state.position = getPosition(this.reference);
+            setStyles(this.popper, { position: this.state.position });
+
+            // compute reference element offsets
+            data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference);
+
+            // compute auto placement, 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 = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper);
+
+            // store the computed placement inside `originalPlacement`
+            data.originalPlacement = this.options.placement;
+
+            // compute the popper offsets
+            data.offsets.popper = getPopperOffsets(this.state, this.popper, data.offsets.reference, data.placement);
+
+            // run the modifiers
+            data = runModifiers(this.modifiers, data);
+
+            // the first `update` will call `onCreate` callback
+            // the other ones will call `onUpdate` callback
+            if (!this.state.isCreated) {
+                this.state.isCreated = true;
+                this.options.onCreate(data);
+            } else {
+                this.options.onUpdate(data);
+            }
+        }
+
+        /**
+         * Schedule an update, it will run on the next UI update available
+         * @method scheduleUpdate
+         * @memberof Popper
+         */
+
+    }, {
+        key: 'destroy',
+
+
+        /**
+         * Destroy the popper
+         * @method
+         * @memberof Popper
+         */
+        value: function destroy() {
+            this.state.isDestroyed = true;
+
+            // touch DOM only if `applyStyle` modifier is enabled
+            if (isModifierEnabled(this.modifiers, 'applyStyle')) {
+                this.popper.removeAttribute('x-placement');
+                this.popper.style.left = '';
+                this.popper.style.position = '';
+                this.popper.style.top = '';
+                this.popper.style[getSupportedPropertyName('transform')] = '';
+            }
+
+            this.disableEventListeners();
+
+            // 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;
+        }
+
+        /**
+         * it will add resize/scroll events and start recalculating
+         * position of the popper element when they are triggered
+         * @method
+         * @memberof Popper
+         */
+
+    }, {
+        key: 'enableEventListeners',
+        value: function enableEventListeners() {
+            if (!this.state.eventsEnabled) {
+                this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate);
+            }
+        }
+
+        /**
+         * it will remove resize/scroll events and won't recalculate
+         * popper position when they are triggered. It also won't trigger onUpdate callback anymore,
+         * unless you call 'update' method manually.
+         * @method
+         * @memberof Popper
+         */
+
+    }, {
+        key: 'disableEventListeners',
+        value: function disableEventListeners() {
+            if (this.state.eventsEnabled) {
+                window.cancelAnimationFrame(this.scheduleUpdate);
+                this.state = removeEventListeners(this.reference, this.state);
+            }
+        }
+
+        /**
+         * Collection of utilities useful when writing custom modifiers
+         * @memberof Popper
+         */
+
+
+        /**
+         * List of accepted placements to use as values of the `placement` option
+         * @memberof Popper
+         */
+
+
+        /**
+         * Default Popper.js options
+         * @memberof Popper
+         */
+
+    }]);
+    return Popper;
+}();
+
+Popper.Utils = Utils;
+Popper.placements = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end'];
+Popper.Defaults = DEFAULTS;
+
+return Popper;
+
+})));
+//# sourceMappingURL=popper.es5.js.map
index d09a505..ff3a01c 100644 (file)
@@ -5,13 +5,13 @@
     define(["jquery","./popper"], function (a0,b1) {
       return (root['Tour'] = factory(a0,b1));
     });
-  } else if (typeof exports === 'object') {
+  } 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(require("jquery"),require("popper.js"));
   } else {
-    root['Tour'] = factory($,Popper);
+    root['Tour'] = factory(root["$"],root["Popper"]);
   }
 }(this, function ($, Popper) {
 
@@ -24,7 +24,7 @@
  * @param   {object}    config  The configuration object.
  */
 
-var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
 
 function Tour(config) {
     this.init(config);
@@ -773,12 +773,8 @@ Tour.prototype.addStepToPage = function (stepConfig) {
         // Add the backdrop.
         this.positionBackdrop(stepConfig);
 
-        if (stepConfig.attachPoint === 'append') {
-            stepConfig.attachTo.append(currentStepNode);
-            this.currentStepNode = currentStepNode;
-        } else {
-            this.currentStepNode = currentStepNode.insertAfter(stepConfig.attachTo);
-        }
+        $(document.body).append(currentStepNode);
+        this.currentStepNode = currentStepNode;
 
         // Ensure that the step node is positioned.
         // Some situations mean that the value is not properly calculated without this step.
@@ -807,7 +803,7 @@ Tour.prototype.addStepToPage = function (stepConfig) {
         currentStepNode.addClass('orphan');
 
         // It lives in the body.
-        stepConfig.attachTo.append(currentStepNode);
+        $(document.body).append(currentStepNode);
         this.currentStepNode = currentStepNode;
 
         this.currentStepNode.offset(this.calculateStepPositionInPage());
@@ -1231,11 +1227,6 @@ Tour.prototype.positionStep = function (stepConfig) {
         }
     };
 
-    var boundaryElement = target.closest('section');
-    if (boundaryElement.length) {
-        config.boundariesElement = boundaryElement[0];
-    }
-
     var background = $('[data-flexitour="step-background"]');
     if (background.length) {
         target = background;
@@ -1311,6 +1302,8 @@ Tour.prototype.positionBackdrop = function (stepConfig) {
             var targetPosition = this.calculatePosition(targetNode);
             if (targetPosition === 'fixed') {
                 background.css('top', 0);
+            } else if (targetPosition === 'absolute') {
+                background.css('position', 'fixed');
             }
 
             var fader = background.clone();
index 0d845ab..7d9c340 100644 (file)
@@ -4,14 +4,14 @@
     <location>amd/src/tour.js</location>
     <name>Flexitour</name>
     <license>GPLv3</license>
-    <version>0.9.10</version>
+    <version>0.10.0</version>
     <licenseversion>3</licenseversion>
   </library>
   <library>
     <location>amd/src/popper.js</location>
     <name>Popper.js</name>
     <license>MIT</license>
-    <version>v1.0.0-alpha.3</version>
+    <version>v1.0.8</version>
     <licenseversion></licenseversion>
   </library>
 </libraries>
index 8da4f1e..ad9bc00 100644 (file)
@@ -44,6 +44,10 @@ class cachestore_redis_addinstance_form extends cachestore_addinstance_form {
         $form->addHelpButton('server', 'server', 'cachestore_redis');
         $form->addRule('server', get_string('required'), 'required');
 
+        $form->addElement('passwordunmask', 'password', get_string('password', 'cachestore_redis'));
+        $form->setType('password', PARAM_RAW);
+        $form->addHelpButton('password', 'password', 'cachestore_redis');
+
         $form->addElement('text', 'prefix', get_string('prefix', 'cachestore_redis'), array('size' => 16));
         $form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
         $form->addHelpButton('prefix', 'prefix', 'cachestore_redis');
@@ -55,4 +59,4 @@ class cachestore_redis_addinstance_form extends cachestore_addinstance_form {
         $form->setDefault('serializer', Redis::SERIALIZER_PHP);
         $form->setType('serializer', PARAM_INT);
     }
-}
\ No newline at end of file
+}
index a44a156..26aad4e 100644 (file)
@@ -34,8 +34,12 @@ $string['serializer_igbinary'] = 'The igbinary serializer.';
 $string['serializer_php'] = 'The default PHP serializer.';
 $string['server'] = 'Server';
 $string['server_help'] = 'This sets the hostname or IP address of the Redis server to use.';
+$string['password'] = 'Password';
+$string['password_help'] = 'This sets the password of the Redis server.';
 $string['test_server'] = 'Test server';
 $string['test_server_desc'] = 'Redis server to use for testing.';
+$string['test_password'] = 'Test server password';
+$string['test_password_desc'] = 'Redis test server password.';
 $string['test_serializer'] = 'Serializer';
 $string['test_serializer_desc'] = 'Serializer to use for testing.';
 $string['useserializer'] = 'Use serializer';
index fd19709..d02938b 100644 (file)
@@ -134,8 +134,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
         if (array_key_exists('serializer', $configuration)) {
             $this->serializer = (int)$configuration['serializer'];
         }
+        $password = !empty($configuration['password']) ? $configuration['password'] : '';
         $prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : '';
-        $this->redis = $this->new_redis($configuration['server'], $prefix);
+        $this->redis = $this->new_redis($configuration['server'], $prefix, $password);
     }
 
     /**
@@ -144,9 +145,10 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
      *
      * @param string $server The server connection string
      * @param string $prefix The key prefix
+     * @param string $password The server connection password
      * @return Redis
      */
-    protected function new_redis($server, $prefix = '') {
+    protected function new_redis($server, $prefix = '', $password = '') {
         $redis = new Redis();
         $port = null;
         if (strpos($server, ':')) {
@@ -155,6 +157,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
             $port = $serverconf[1];
         }
         if ($redis->connect($server, $port)) {
+            if (!empty($password)) {
+                $redis->auth($password);
+            }
             $redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
             if (!empty($prefix)) {
                 $redis->setOption(Redis::OPT_PREFIX, $prefix);
index 0787815..67f651c 100644 (file)
@@ -34,6 +34,14 @@ $settings->add(
         16
     )
 );
+$settings->add(
+    new admin_setting_configpasswordunmask(
+        'cachestore_redis/test_password',
+        get_string('test_password', 'cachestore_redis'),
+        get_string('test_password_desc', 'cachestore_redis'),
+        ''
+    )
+);
 
 if (class_exists('Redis')) { // Only if Redis is available.
 
index 432a85a..3a8afba 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version    = 2016120500;
+$plugin->version    = 2017031900;
 $plugin->requires   = 2016112900; // Requires this Moodle version (3.0.4).
 $plugin->maturity   = MATURITY_STABLE;
 $plugin->component  = 'cachestore_redis';
index 41fd950..a6882a0 100644 (file)
@@ -265,6 +265,7 @@ $CFG->admin = 'admin';
 //      $CFG->session_redis_host = '127.0.0.1';
 //      $CFG->session_redis_port = 6379;  // Optional.
 //      $CFG->session_redis_database = 0;  // Optional, default is db 0.
+//      $CFG->session_redis_auth = ''; // Optional, default is don't set one.
 //      $CFG->session_redis_prefix = ''; // Optional, default is don't set one.
 //      $CFG->session_redis_acquire_lock_timeout = 120;
 //      $CFG->session_redis_lock_expire = 7200;
index 7923908..32793d3 100644 (file)
@@ -118,8 +118,8 @@ class converter {
                 // Unable to fetch the converter instance.
                 // Reset the status back to PENDING so that it may be picked up again.
                 $conversion->set('status', conversion::STATUS_PENDING);
-                $conversion->update();
             }
+            $conversion->update();
         }
 
         // Refresh the status.
@@ -134,6 +134,7 @@ class converter {
             if (!$currentconverter) {
                 // No more converters available.
                 $conversion->set('status', conversion::STATUS_FAILED);
+                $conversion->update();
                 return $this;
             }
 
index a8679e2..bbcedf0 100644 (file)
@@ -105,10 +105,6 @@ class converter implements \core_files\converter_interface {
             return $this;
         }
 
-        // Update the status to IN_PROGRESS.
-        $conversion->set('status', \core_files\conversion::STATUS_IN_PROGRESS);
-        $conversion->update();
-
         // Copy the file to the tmp dir.
         $uniqdir = make_unique_writable_directory(make_temp_directory('core_file/conversions'));
         \core_shutdown_manager::register_function('remove_dir', array($uniqdir));
index 6f182c6..950d593 100644 (file)
@@ -417,6 +417,7 @@ class core_files_converter_testcase extends advanced_testcase {
         $conversion->set_sourcefile($file);
         $conversion->set('targetformat', 'target');
         $conversion->set('status', conversion::STATUS_IN_PROGRESS);
+        $conversion->create();
 
         $converterinstance = $this->get_mocked_converter([
                 'poll_conversion_status',
@@ -484,6 +485,7 @@ class core_files_converter_testcase extends advanced_testcase {
             'sourcefileid' => $file->get_id(),
             'targetformat' => 'target',
         ]);
+        $conversion->create();
 
         $converter->poll_conversion($conversion);
 
index 6a1bef7..9025ce4 100644 (file)
@@ -220,7 +220,7 @@ function behat_clean_init_config() {
         'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
         'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
         'proxybypass', 'theme', 'pathtogs', 'pathtodu', 'aspellpath', 'pathtodot', 'skiplangupgrade',
-        'altcacheconfigpath', 'pathtounoconv'
+        'altcacheconfigpath', 'pathtounoconv', 'alternative_file_system_class'
     ));
 
     // Add extra allowed settings.
index be6e020..993275f 100644 (file)
@@ -240,14 +240,6 @@ class icon_system_fontawesome extends icon_system_font {
             'core:i/checkpermissions' => 'fa-unlock-alt',
             'core:i/cohort' => 'fa-users',
             'core:i/competencies' => 'fa-check-square-o',
-            'core:i/completion-auto-enabled' => 'fa-check-square',
-            'core:i/completion-auto-fail' => 'fa-square-o',
-            'core:i/completion-auto-n' => 'fa-square-o',
-            'core:i/completion-auto-pass' => 'fa-check-square',
-            'core:i/completion-auto-y' => 'fa-check-square',
-            'core:i/completion-manual-enabled' => 'fa-square-o',
-            'core:i/completion-manual-y' => 'fa-check-square',
-            'core:i/completion-manual-n' => 'fa-square-o',
             'core:i/completion_self' => 'fa-user-o',
             'core:i/lock' => 'fa-lock',
             'core:i/courseevent' => 'fa-calendar',
@@ -279,7 +271,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:i/hierarchylock' => 'fa-lock',
             'core:i/import' => 'fa-level-up',
             'core:i/info' => 'fa-info',
-            'core:i/invalid' => 'fa-exclamation text-danger',
+            'core:i/invalid' => 'fa-times text-danger',
             'core:i/item' => 'fa-circle',
             'core:i/loading' => 'fa-circle-o-notch fa-spin',
             'core:i/loading_small' => 'fa-circle-o-notch fa-spin',
@@ -287,8 +279,8 @@ class icon_system_fontawesome extends icon_system_font {
             'core:i/log' => 'fa-list-alt',
             'core:i/mahara_host' => 'fa-id-badge',
             'core:i/manual_item' => 'fa-square-o',
-            'core:i/marked' => 'fa-check-square',
-            'core:i/marker' => 'fa-user-o',
+            'core:i/marked' => 'fa-circle',
+            'core:i/marker' => 'fa-circle-o',
             'core:i/mean' => 'fa-calculator',
             'core:i/menu' => 'fa-ellipsis-v',
             'core:i/mnethost' => 'fa-external-link',
@@ -342,7 +334,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:i/userevent' => 'fa-user',
             'core:i/user' => 'fa-user',
             'core:i/users' => 'fa-users',
-            'core:i/valid' => 'fa-check-square-o text-success',
+            'core:i/valid' => 'fa-check text-success',
             'core:i/warning' => 'fa-exclamation text-warning',
             'core:i/withsubcat' => 'fa-plus-square',
             'core:m/USD' => 'fa-usd',
index d90ed8b..780a3f4 100644 (file)
@@ -44,6 +44,8 @@ class redis extends handler {
     protected $host = '';
     /** @var int $port The port to connect to */
     protected $port = 6379;
+    /** @var string $auth redis password  */
+    protected $auth = '';
     /** @var int $database the Redis database to store sesions in */
     protected $database = 0;
     /** @var array $servers list of servers parsed from save_path */
@@ -81,6 +83,10 @@ class redis extends handler {
             $this->port = (int)$CFG->session_redis_port;
         }
 
+        if (isset($CFG->session_redis_auth)) {
+            $this->auth = $CFG->session_redis_auth;
+        }
+
         if (isset($CFG->session_redis_database)) {
             $this->database = (int)$CFG->session_redis_database;
         }
@@ -156,6 +162,13 @@ class redis extends handler {
             if (!$this->connection->connect($this->host, $this->port, 1)) {
                 throw new RedisException('Unable to connect to host.');
             }
+
+            if ($this->auth !== '') {
+                if (!$this->connection->auth($this->auth)) {
+                    throw new RedisException('Unable to authenticate.');
+                }
+            }
+
             if (!$this->connection->setOption(\Redis::OPT_SERIALIZER, $this->serializer)) {
                 throw new RedisException('Unable to set Redis PHP Serializer option.');
             }
index 6b2aeed..7af71ca 100644 (file)
@@ -56,7 +56,8 @@ $functions = array(
         'description' => 'Create calendar events',
         'classpath' => 'calendar/externallib.php',
         'type' => 'write',
-        'capabilities' => 'moodle/calendar:manageentries, moodle/calendar:manageownentries, moodle/calendar:managegroupentries'
+        'capabilities' => 'moodle/calendar:manageentries, moodle/calendar:manageownentries, moodle/calendar:managegroupentries',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'core_calendar_delete_calendar_events' => array(
         'classname' => 'core_calendar_external',
@@ -64,7 +65,8 @@ $functions = array(
         'description' => 'Delete calendar events',
         'classpath' => 'calendar/externallib.php',
         'type' => 'write',
-        'capabilities' => 'moodle/calendar:manageentries, moodle/calendar:manageownentries, moodle/calendar:managegroupentries'
+        'capabilities' => 'moodle/calendar:manageentries, moodle/calendar:manageownentries, moodle/calendar:managegroupentries',
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'core_calendar_get_calendar_events' => array(
         'classname' => 'core_calendar_external',
index c3ab0c8..dd84448 100644 (file)
@@ -2611,7 +2611,78 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2017031400.00);
     }
 
-    if ($oldversion < 2017033100.01) {
+    if ($oldversion < 2017040400.00) {
+
+        // If the 'Course overview' block is no longer present, replace with the 'My overview' block.
+        if (!file_exists($CFG->dirroot . '/blocks/course_overview/block_course_overview.php')) {
+            $DB->set_field('block_instances', 'blockname', 'myoverview', array('blockname' => 'course_overview'));
+        }
+
+        upgrade_main_savepoint(true, 2017040400.00);
+    }
+
+    if ($oldversion < 2017040401.00) {
+
+        // If the 'Course overview' block is no longer present, remove it.
+        // Note - we do not need to completely remove the block context etc because we
+        // have replaced all occurrences of the 'Course overview' block with the 'My overview'
+        // block in the upgrade step above.
+        if (!file_exists($CFG->dirroot . '/blocks/course_overview/block_course_overview.php')) {
+            // Delete the block from the block table.
+            $DB->delete_records('block', array('name' => 'course_overview'));
+            // Remove capabilities.
+            capabilities_cleanup('block_course_overview');
+            // Clean config.
+            unset_all_config_for_plugin('block_course_overview');
+        }
+
+        upgrade_main_savepoint(true, 2017040401.00);
+    }
+
+    if ($oldversion < 2017040402.00) {
+
+        // Define fields to be added to the 'event' table.
+        $table = new xmldb_table('event');
+        $fieldtype = new xmldb_field('type', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, 0, 'instance');
+        $fieldtimesort = new xmldb_field('timesort', XMLDB_TYPE_INTEGER, '10', null, false, null, null, 'timeduration');
+
+        // Conditionally launch add field.
+        if (!$dbman->field_exists($table, $fieldtype)) {
+            $dbman->add_field($table, $fieldtype);
+        }
+
+        // Conditionally launch add field.
+        if (!$dbman->field_exists($table, $fieldtimesort)) {
+            $dbman->add_field($table, $fieldtimesort);
+        }
+
+        // Now, define the index we will be adding.
+        $index = new xmldb_index('type-timesort', XMLDB_INDEX_NOTUNIQUE, array('type', 'timesort'));
+
+        // Conditionally launch add index.
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        upgrade_main_savepoint(true, 2017040402.00);
+    }
+
+    if ($oldversion < 2017040403.00) {
+        // Create adhoc task for upgrading of existing calendar events.
+        $record = new \stdClass();
+        $record->classname = "\\core\\task\\refresh_mod_calendar_events_task";
+        $record->component = 'core';
+
+        // Next run time based from nextruntime computation in \core\task\manager::queue_adhoc_task().
+        $nextruntime = time() - 1;
+        $record->nextruntime = $nextruntime;
+        $DB->insert_record('task_adhoc', $record);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2017040403.00);
+    }
+
+    if ($oldversion < 2017040700.01) {
 
         // Define table oauth2_issuer to be created.
         $table = new xmldb_table('oauth2_issuer');
@@ -2645,10 +2716,10 @@ function xmldb_main_upgrade($oldversion) {
         }
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2017033100.01);
+        upgrade_main_savepoint(true, 2017040700.01);
     }
 
-    if ($oldversion < 2017033100.02) {
+    if ($oldversion < 2017040700.02) {
 
         // Define table oauth2_endpoint to be created.
         $table = new xmldb_table('oauth2_endpoint');
@@ -2672,10 +2743,10 @@ function xmldb_main_upgrade($oldversion) {
         }
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2017033100.02);
+        upgrade_main_savepoint(true, 2017040700.02);
     }
 
-    if ($oldversion < 2017033100.03) {
+    if ($oldversion < 2017040700.03) {
 
         // Define table oauth2_system_account to be created.
         $table = new xmldb_table('oauth2_system_account');
@@ -2701,10 +2772,10 @@ function xmldb_main_upgrade($oldversion) {
         }
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2017033100.03);
+        upgrade_main_savepoint(true, 2017040700.03);
     }
 
-    if ($oldversion < 2017033100.04) {
+    if ($oldversion < 2017040700.04) {
 
         // Define table oauth2_user_field_mapping to be created.
         $table = new xmldb_table('oauth2_user_field_mapping');
@@ -2729,78 +2800,7 @@ function xmldb_main_upgrade($oldversion) {
         }
 
         // Main savepoint reached.
-        upgrade_main_savepoint(true, 2017033100.04);
-    }
-
-    if ($oldversion < 2017040400.00) {
-
-        // If the 'Course overview' block is no longer present, replace with the 'My overview' block.
-        if (!file_exists($CFG->dirroot . '/blocks/course_overview/block_course_overview.php')) {
-            $DB->set_field('block_instances', 'blockname', 'myoverview', array('blockname' => 'course_overview'));
-        }
-
-        upgrade_main_savepoint(true, 2017040400.00);
-    }
-
-    if ($oldversion < 2017040401.00) {
-
-        // If the 'Course overview' block is no longer present, remove it.
-        // Note - we do not need to completely remove the block context etc because we
-        // have replaced all occurrences of the 'Course overview' block with the 'My overview'
-        // block in the upgrade step above.
-        if (!file_exists($CFG->dirroot . '/blocks/course_overview/block_course_overview.php')) {
-            // Delete the block from the block table.
-            $DB->delete_records('block', array('name' => 'course_overview'));
-            // Remove capabilities.
-            capabilities_cleanup('block_course_overview');
-            // Clean config.
-            unset_all_config_for_plugin('block_course_overview');
-        }
-
-        upgrade_main_savepoint(true, 2017040401.00);
-    }
-
-    if ($oldversion < 2017040402.00) {
-
-        // Define fields to be added to the 'event' table.
-        $table = new xmldb_table('event');
-        $fieldtype = new xmldb_field('type', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, 0, 'instance');
-        $fieldtimesort = new xmldb_field('timesort', XMLDB_TYPE_INTEGER, '10', null, false, null, null, 'timeduration');
-
-        // Conditionally launch add field.
-        if (!$dbman->field_exists($table, $fieldtype)) {
-            $dbman->add_field($table, $fieldtype);
-        }
-
-        // Conditionally launch add field.
-        if (!$dbman->field_exists($table, $fieldtimesort)) {
-            $dbman->add_field($table, $fieldtimesort);
-        }
-
-        // Now, define the index we will be adding.
-        $index = new xmldb_index('type-timesort', XMLDB_INDEX_NOTUNIQUE, array('type', 'timesort'));
-
-        // Conditionally launch add index.
-        if (!$dbman->index_exists($table, $index)) {
-            $dbman->add_index($table, $index);
-        }
-
-        upgrade_main_savepoint(true, 2017040402.00);
-    }
-
-    if ($oldversion < 2017040403.00) {
-        // Create adhoc task for upgrading of existing calendar events.
-        $record = new \stdClass();
-        $record->classname = "\\core\\task\\refresh_mod_calendar_events_task";
-        $record->component = 'core';
-
-        // Next run time based from nextruntime computation in \core\task\manager::queue_adhoc_task().
-        $nextruntime = time() - 1;
-        $record->nextruntime = $nextruntime;
-        $DB->insert_record('task_adhoc', $record);
-
-        // Main savepoint reached.
-        upgrade_main_savepoint(true, 2017040403.00);
+        upgrade_main_savepoint(true, 2017040700.04);
     }
 
     return true;
index 4927cc6..aa92af8 100644 (file)
@@ -186,7 +186,7 @@ $allowed = array('wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermission
                  'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions',
                  'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass', // keep proxy settings from config.php
                  'altcacheconfigpath', 'pathtogs', 'pathtodu', 'aspellpath', 'pathtodot',
-                 'pathtounoconv'
+                 'pathtounoconv', 'alternative_file_system_class'
                 );
 $productioncfg = (array)$CFG;
 $CFG = new stdClass();
index a414fae..42cd823 100644 (file)
@@ -1399,7 +1399,8 @@ function stats_get_report_options($courseid,$mode) {
             $sql = 'SELECT r.id, r.name FROM {role} r JOIN {stats_daily} s ON s.roleid = r.id WHERE s.courseid = :courseid GROUP BY r.id, r.name';
             if ($roles = $DB->get_records_sql($sql, array('courseid' => $courseid))) {
                 foreach ($roles as $role) {
-                    $reportoptions[STATS_REPORT_ACTIVITYBYROLE.$role->id] = get_string('statsreport'.STATS_REPORT_ACTIVITYBYROLE). ' '.$role->name;
+                    $reportoptions[STATS_REPORT_ACTIVITYBYROLE.$role->id] = get_string('statsreport'.STATS_REPORT_ACTIVITYBYROLE).
+                        ' ' . format_string($role->name, true, ['context' => $context]);
                 }
             }
         }
index 13e72cb..1c68a5d 100644 (file)
@@ -7238,7 +7238,9 @@ class assign {
         }
 
         // Do not show if we are editing a previous attempt.
-        if ($attemptnumber == -1 && $this->get_instance()->attemptreopenmethod != ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
+        if (($attemptnumber == -1 ||
+            ($attemptnumber + 1) == count($this->get_all_submissions($userid))) &&
+            $this->get_instance()->attemptreopenmethod != ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
             $mform->addElement('header', 'attemptsettings', get_string('attemptsettings', 'assign'));
             $attemptreopenmethod = get_string('attemptreopenmethod_' . $this->get_instance()->attemptreopenmethod, 'assign');
             $mform->addElement('static', 'attemptreopenmethod', get_string('attemptreopenmethod', 'assign'), $attemptreopenmethod);
index 860db56..74b11af 100644 (file)
@@ -304,15 +304,10 @@ function book_supports($feature) {
 function book_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $booknode) {
     global $USER, $PAGE, $OUTPUT;
 
-    $plugins = core_component::get_plugin_list('booktool');
-    foreach ($plugins as $plugin => $dir) {
-        if (file_exists("$dir/lib.php")) {
-            require_once("$dir/lib.php");
-        }
-        $function = 'booktool_'.$plugin.'_extend_settings_navigation';
-        if (function_exists($function)) {
-            $function($settingsnav, $booknode);
-        }
+    if ($booknode->children->count() > 0) {
+        $firstkey = $booknode->children->get_key_list()[0];
+    } else {
+        $firstkey = null;
     }
 
     $params = $PAGE->url->params();
@@ -327,9 +322,21 @@ function book_extend_settings_navigation(settings_navigation $settingsnav, navig
             $edit = '1';
         }
         $url = new moodle_url('/mod/book/view.php', array('id'=>$params['id'], 'chapterid'=>$params['chapterid'], 'edit'=>$edit, 'sesskey'=>sesskey()));
-        $booknode->add($string, $url, navigation_node::TYPE_SETTING);
+        $editnode = navigation_node::create($string, $url, navigation_node::TYPE_SETTING);
+        $booknode->add_node($editnode, $firstkey);
         $PAGE->set_button($OUTPUT->single_button($url, $string));
     }
+
+    $plugins = core_component::get_plugin_list('booktool');
+    foreach ($plugins as $plugin => $dir) {
+        if (file_exists("$dir/lib.php")) {
+            require_once("$dir/lib.php");
+        }
+        $function = 'booktool_'.$plugin.'_extend_settings_navigation';
+        if (function_exists($function)) {
+            $function($settingsnav, $booknode);
+        }
+    }
 }
 
 
index d3cca99..f380f39 100644 (file)
@@ -33,6 +33,7 @@ use mod_feedback\external\feedback_completedtmp_exporter;
 use mod_feedback\external\feedback_item_exporter;
 use mod_feedback\external\feedback_valuetmp_exporter;
 use mod_feedback\external\feedback_value_exporter;
+use mod_feedback\external\feedback_completed_exporter;
 
 /**
  * Feedback external functions
@@ -992,10 +993,12 @@ class mod_feedback_external extends external_api {
      * @since Moodle 3.3
      */
     public static function get_non_respondents($feedbackid, $groupid = 0, $sort = 'lastaccess', $page = 0, $perpage = 0) {
+        global $CFG;
+        require_once($CFG->dirroot . '/mod/feedback/lib.php');
 
         $params = array('feedbackid' => $feedbackid, 'groupid' => $groupid, 'sort' => $sort, 'page' => $page, 'perpage' => $perpage);
         $params = self::validate_parameters(self::get_non_respondents_parameters(), $params);
-        $warnings = $itemsdata = array();
+        $warnings = $nonrespondents = array();
 
         list($feedback, $course, $cm, $context) = self::validate_feedback($params['feedbackid']);
 
@@ -1024,20 +1027,28 @@ class mod_feedback_external extends external_api {
         if ($params['sort'] !== 'firstname' && $params['sort'] !== 'lastname' && $params['sort'] !== 'lastaccess') {
             throw new invalid_parameter_exception('Invalid sort param, must be firstname, lastname or lastaccess.');
         }
-        $params['sort'] = 'u.' . $params['sort'];
 
         // Check if we are page filtering.
-        if ($params['page'] == 0 && $params['perpage'] == 0) {
-            $perpage = false;
-            $page = false;
+        if ($params['perpage'] == 0) {
+            $page = $params['page'];
+            $perpage = FEEDBACK_DEFAULT_PAGE_COUNT;
         } else {
             $perpage = $params['perpage'];
             $page = $perpage * $params['page'];
         }
-        $users = feedback_get_incomplete_users($cm, $groupid, $params['sort'], $page, $perpage);
+        $users = feedback_get_incomplete_users($cm, $groupid, $params['sort'], $page, $perpage, true);
+        foreach ($users as $user) {
+            $nonrespondents[] = [
+                'courseid' => $course->id,
+                'userid'   => $user->id,
+                'fullname' => fullname($user),
+                'started'  => $user->feedbackstarted
+            ];
+        }
 
         $result = array(
-            'users' => $users,
+            'users' => $nonrespondents,
+            'total' => feedback_count_incomplete_users($cm, $groupid),
             'warnings' => $warnings
         );
         return $result;
@@ -1052,10 +1063,18 @@ class mod_feedback_external extends external_api {
     public static function get_non_respondents_returns() {
         return new external_single_structure(
             array(
-            'users' => new external_multiple_structure(
-                new external_value(PARAM_INT, 'The user id')
-            ),
-            'warnings' => new external_warnings(),
+                'users' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'courseid' => new external_value(PARAM_INT, 'Course id'),
+                            'userid' => new external_value(PARAM_INT, 'The user id'),
+                            'fullname' => new external_value(PARAM_TEXT, 'User full name'),
+                            'started' => new external_value(PARAM_BOOL, 'If the user has started the attempt'),
+                        )
+                    )
+                ),
+                'total' => new external_value(PARAM_INT, 'Total number of non respondents'),
+                'warnings' => new external_warnings(),
             )
         );
     }
@@ -1181,4 +1200,64 @@ class mod_feedback_external extends external_api {
             )
         );
     }
+
+    /**
+     * Describes the parameters for get_last_completed.
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.3
+     */
+    public static function get_last_completed_parameters() {
+        return new external_function_parameters (
+            array(
+                'feedbackid' => new external_value(PARAM_INT, 'Feedback instance id'),
+            )
+        );
+    }
+
+    /**
+     * Retrieves the last completion record for the current user.
+     *
+     * @param int $feedbackid feedback instance id
+     * @return array of warnings and the last completed record
+     * @since Moodle 3.3
+     * @throws moodle_exception
+     */
+    public static function get_last_completed($feedbackid) {
+        global $PAGE;
+
+        $params = array('feedbackid' => $feedbackid);
+        $params = self::validate_parameters(self::get_last_completed_parameters(), $params);
+        $warnings = array();
+
+        list($feedback, $course, $cm, $context) = self::validate_feedback($params['feedbackid']);
+        $feedbackcompletion = new mod_feedback_completion($feedback, $cm, $course->id);
+
+        if ($feedbackcompletion->is_anonymous()) {
+             throw new moodle_exception('anonymous', 'feedback');
+        }
+        if ($completed = $feedbackcompletion->find_last_completed()) {
+            $exporter = new feedback_completed_exporter($completed);
+            return array(
+                'completed' => $exporter->export($PAGE->get_renderer('core')),
+                'warnings' => $warnings,
+            );
+        }
+        throw new moodle_exception('not_completed_yet', 'feedback');
+    }
+
+    /**
+     * Describes the get_last_completed return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.3
+     */
+    public static function get_last_completed_returns() {
+        return new external_single_structure(
+            array(
+                'completed' => feedback_completed_exporter::get_read_structure(),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
 }
diff --git a/mod/feedback/classes/external/feedback_completed_exporter.php b/mod/feedback/classes/external/feedback_completed_exporter.php
new file mode 100644 (file)
index 0000000..2c5d9f5
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Class for exporting a feedback completion record.
+ *
+ * @package    mod_feedback
+ * @copyright  2017 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_feedback\external;
+defined('MOODLE_INTERNAL') || die();
+
+use core\external\exporter;
+
+/**
+ * Class for exporting a feedback completion record.
+ *
+ * @copyright  2017 Juan Leyva <juan@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class feedback_completed_exporter extends exporter {
+
+    /**
+     * Return the list of properties.
+     *
+     * @return array list of properties
+     */
+    protected static function define_properties() {
+        return array(
+            'id' => array(
+                'type' => PARAM_INT,
+                'description' => 'The record id.',
+            ),
+            'feedback' => array(
+                'type' => PARAM_INT,
+                'description' => 'The feedback instance id this records belongs to.',
+            ),
+            'userid' => array(
+                'type' => PARAM_INT,
+                'description' => 'The user who completed the feedback (0 for anonymous).',
+            ),
+            'timemodified' => array(
+                'type' => PARAM_INT,
+                'description' => 'The last time the feedback was completed.',
+            ),
+            'random_response' => array(
+                'type' => PARAM_INT,
+                'description' => 'The response number (used when shuffling anonymous responses).',
+            ),
+            'anonymous_response' => array(
+                'type' => PARAM_INT,
+                'description' => 'Whether is an anonymous response.',
+            ),
+            'courseid' => array(
+                'type' => PARAM_INT,
+                'description' => 'The course id where the feedback was completed.',
+            ),
+        );
+    }
+}
index 20f3d2c..6866bea 100644 (file)
@@ -133,4 +133,12 @@ $functions = array(
         'capabilities'  => 'mod/feedback:viewreports',
         'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
     ),
+    'mod_feedback_get_last_completed' => array(
+        'classname'     => 'mod_feedback_external',
+        'methodname'    => 'get_last_completed',
+        'description'   => 'Retrieves the last completion record for the current user.',
+        'type'          => 'read',
+        'capabilities'  => 'mod/feedback:view',
+        'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+    ),
 );
index 13418e0..176f304 100644 (file)
@@ -8,7 +8,6 @@ movedown_item,mod_feedback
 move_here,mod_feedback
 moveup_item,mod_feedback
 notavailable,mod_feedback
-not_completed_yet,mod_feedback
 parameters_missing,mod_feedback
 picture,mod_feedback
 picture_file_list,mod_feedback
index a67588d..d0b2ff6 100644 (file)
@@ -191,6 +191,7 @@ $string['no_items_available_yet'] = 'No questions have been set up yet';
 $string['non_anonymous'] = 'User\'s name will be logged and shown with answers';
 $string['non_anonymous_entries'] = 'Non anonymous entries ({$a})';
 $string['non_respondents_students'] = 'Non respondents students ({$a})';
+$string['not_completed_yet'] = 'Not completed yet';
 $string['not_started'] = 'Not started';
 $string['no_templates_available_yet'] = 'No templates available yet';
 $string['not_selected'] = 'Not selected';
@@ -279,7 +280,6 @@ $string['cancel_moving'] = 'Cancel moving';
 $string['movedown_item'] = 'Move this question down';
 $string['move_here'] = 'Move here';
 $string['moveup_item'] = 'Move this question up';
-$string['not_completed_yet'] = 'Not completed yet';
 $string['notavailable'] = 'this feedback is not available';
 $string['saving_failed_because_missing_or_false_values'] = 'Saving failed because missing or false values';
 $string['cannotunmap'] = 'Database problem, unable to unmap';
index 8ae3d32..3fe661a 100644 (file)
@@ -971,13 +971,15 @@ function feedback_check_is_switchrole() {
  * @param string $sort
  * @param int $startpage
  * @param int $pagecount
- * @return object the userrecords
+ * @param bool $includestatus to return if the user started or not the feedback among the complete user record
+ * @return array array of user ids or user objects when $includestatus set to true
  */
 function feedback_get_incomplete_users(cm_info $cm,
                                        $group = false,
                                        $sort = '',
                                        $startpage = false,
-                                       $pagecount = false) {
+                                       $pagecount = false,
+                                       $includestatus = false) {
 
     global $DB;
 
@@ -985,7 +987,8 @@ function feedback_get_incomplete_users(cm_info $cm,
 
     //first get all user who can complete this feedback
     $cap = 'mod/feedback:complete';
-    $fields = 'u.id, u.username';
+    $allnames = get_all_user_name_fields(true, 'u');
+    $fields = 'u.id, ' . $allnames . ', u.picture, u.email, u.imagealt';
     if (!$allusers = get_users_by_capability($context,
                                             $cap,
                                             $fields,
@@ -999,25 +1002,35 @@ function feedback_get_incomplete_users(cm_info $cm,
     }
     // Filter users that are not in the correct group/grouping.
     $info = new \core_availability\info_module($cm);
-    $allusers = $info->filter_user_list($allusers);
+    $allusersrecords = $info->filter_user_list($allusers);
 
-    $allusers = array_keys($allusers);
+    $allusers = array_keys($allusersrecords);
 
     //now get all completeds
     $params = array('feedback'=>$cm->instance);
-    if (!$completedusers = $DB->get_records_menu('feedback_completed', $params, '', 'id, userid')) {
-        return $allusers;
+    if ($completedusers = $DB->get_records_menu('feedback_completed', $params, '', 'id, userid')) {
+        // Now strike all completedusers from allusers.
+        $allusers = array_diff($allusers, $completedusers);
     }
 
-    //now strike all completedusers from allusers
-    $allusers = array_diff($allusers, $completedusers);
-
     //for paging I use array_slice()
     if ($startpage !== false AND $pagecount !== false) {
         $allusers = array_slice($allusers, $startpage, $pagecount);
     }
 
-    return $allusers;
+    // Check if we should return the full users objects.
+    if ($includestatus) {
+        $userrecords = [];
+        $startedusers = $DB->get_records_menu('feedback_completedtmp', ['feedback' => $cm->instance], '', 'id, userid');
+        $startedusers = array_flip($startedusers);
+        foreach ($allusers as $userid) {
+            $allusersrecords[$userid]->feedbackstarted = isset($startedusers[$userid]);
+            $userrecords[] = $allusersrecords[$userid];
+        }
+        return $userrecords;
+    } else {    // Return just user ids.
+        return $allusers;
+    }
 }
 
 /**
index 340380b..5339e9d 100644 (file)
@@ -216,36 +216,38 @@ if ($showall) {
     $pagecount = $table->get_page_size();
 }
 
-$students = feedback_get_incomplete_users($cm, $usedgroupid, $sort, $startpage, $pagecount);
+// Return students record including if they started or not the feedback.
+$students = feedback_get_incomplete_users($cm, $usedgroupid, $sort, $startpage, $pagecount, true);
 //####### viewreports-start
 //print the list of students
 echo $OUTPUT->heading(get_string('non_respondents_students', 'feedback', $matchcount), 4);
 echo isset($groupselect) ? $groupselect : '';
 echo '<div class="clearer"></div>';
 
-if (!$students) {
+if (empty($students)) {
     echo $OUTPUT->notification(get_string('noexistingparticipants', 'enrol'));
 } else {
 
-    if (has_capability('moodle/course:bulkmessaging', $coursecontext)) {
+    $canbulkmessaging = has_capability('moodle/course:bulkmessaging', $coursecontext);
+    if ($canbulkmessaging) {
         echo '<form class="mform" action="show_nonrespondents.php" method="post" id="feedback_sendmessageform">';
     }
+
     foreach ($students as $student) {
-        $user = $DB->get_record('user', array('id'=>$student));
         //userpicture and link to the profilepage
-        $profile_url = $CFG->wwwroot.'/user/view.php?id='.$user->id.'&amp;course='.$course->id;
-        $profilelink = '<strong><a href="'.$profile_url.'">'.fullname($user).'</a></strong>';
-        $data = array ($OUTPUT->user_picture($user, array('courseid'=>$course->id)), $profilelink);
+        $profileurl = $CFG->wwwroot.'/user/view.php?id='.$student->id.'&amp;course='.$course->id;
+        $profilelink = '<strong><a href="'.$profileurl.'">'.fullname($student).'</a></strong>';
+        $data = array($OUTPUT->user_picture($student, array('courseid' => $course->id)), $profilelink);
 
-        if ($DB->record_exists('feedback_completedtmp', array('userid' => $user->id, 'feedback' => $feedback->id))) {
+        if ($student->feedbackstarted) {
             $data[] = get_string('started', 'feedback');
         } else {
             $data[] = get_string('not_started', 'feedback');
         }
 
         //selections to bulk messaging
-        if (has_capability('moodle/course:bulkmessaging', $coursecontext)) {
-            $data[] = '<input type="checkbox" class="usercheckbox" name="messageuser[]" value="'.$user->id.'" />';
+        if ($canbulkmessaging) {
+            $data[] = '<input type="checkbox" class="usercheckbox" name="messageuser[]" value="'.$student->id.'" />';
         }
         $table->add_data($data);
     }
index da2f720..3763d59 100644 (file)
@@ -705,7 +705,7 @@ class mod_feedback_external_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(mod_feedback_external::get_non_respondents_returns(), $result);
         $this->assertCount(0, $result['warnings']);
         $this->assertCount(1, $result['users']);
-        $this->assertEquals($anotherstudent->id, $result['users'][0]);
+        $this->assertEquals($anotherstudent->id, $result['users'][0]['userid']);
 
         // Create another student.
         $anotherstudent2 = self::getDataGenerator()->create_user();
@@ -833,4 +833,91 @@ class mod_feedback_external_testcase extends externallib_advanced_testcase {
             $this->assertNotEmpty($attempt['userid']);  // Is not anonymous.
         }
     }
+
+    /**
+     * Test get_last_completed for feedback anonymous not completed.
+     */
+    public function test_get_last_completed_anonymous_not_completed() {
+        global $DB;
+
+        // Force anonymous.
+        $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_YES, array('id' => $this->feedback->id));
+
+        // Test user with full capabilities that didn't complete the feedback.
+        $this->setUser($this->student);
+
+        $this->expectExceptionMessage(get_string('anonymous', 'feedback'));
+        $this->expectException('moodle_exception');
+        mod_feedback_external::get_last_completed($this->feedback->id);
+    }
+
+    /**
+     * Test get_last_completed for feedback anonymous and completed.
+     */
+    public function test_get_last_completed_anonymous_completed() {
+        global $DB;
+
+        // Force anonymous.
+        $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_YES, array('id' => $this->feedback->id));
+        // Add one completion record..
+        $record = [
+            'feedback' => $this->feedback->id,
+            'userid' => $this->student->id,
+            'timemodified' => time() - DAYSECS,
+            'random_response' => 0,
+            'anonymous_response' => FEEDBACK_ANONYMOUS_YES,
+            'courseid' => $this->course->id,
+        ];
+        $record['id'] = $DB->insert_record('feedback_completed', (object) $record);
+
+        // Test user with full capabilities.
+        $this->setUser($this->student);
+
+        $this->expectExceptionMessage(get_string('anonymous', 'feedback'));
+        $this->expectException('moodle_exception');
+        mod_feedback_external::get_last_completed($this->feedback->id);
+    }
+
+    /**
+     * Test get_last_completed for feedback not anonymous and completed.
+     */
+    public function test_get_last_completed_not_anonymous_completed() {
+        global $DB;
+
+        // Force non anonymous.
+        $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
+        // Add one completion record..
+        $record = [
+            'feedback' => $this->feedback->id,
+            'userid' => $this->student->id,
+            'timemodified' => time() - DAYSECS,
+            'random_response' => 0,
+            'anonymous_response' => FEEDBACK_ANONYMOUS_NO,
+            'courseid' => $this->course->id,
+        ];
+        $record['id'] = $DB->insert_record('feedback_completed', (object) $record);
+
+        // Test user with full capabilities.
+        $this->setUser($this->student);
+        $result = mod_feedback_external::get_last_completed($this->feedback->id);
+        $result = external_api::clean_returnvalue(mod_feedback_external::get_last_completed_returns(), $result);
+        $this->assertEquals($record, $result['completed']);
+    }
+
+    /**
+     * Test get_last_completed for feedback not anonymous and not completed.
+     */
+    public function test_get_last_completed_not_anonymous_not_completed() {
+        global $DB;
+
+        // Force anonymous.
+        $DB->set_field('feedback', 'anonymous', FEEDBACK_ANONYMOUS_NO, array('id' => $this->feedback->id));
+
+        // Test user with full capabilities that didn't complete the feedback.
+        $this->setUser($this->student);
+
+        $this->expectExceptionMessage(get_string('not_completed_yet', 'feedback'));
+        $this->expectException('moodle_exception');
+        mod_feedback_external::get_last_completed($this->feedback->id);
+    }
 }
index c644989..1bec366 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017032803;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2017032804;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2016112900;    // Requires this Moodle version
 $plugin->component = 'mod_feedback';   // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index a598437..ce24f2d 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2017040700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2017041000.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.