diff --git a/.gitignore b/.gitignore
new file mode 120000
index 0000000..a3e53f5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+dev-lib/git/.gitignore
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..e366f9f
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "dev-lib"]
+ path = dev-lib
+ url = https://github.com/WordPoints/dev-lib.git
diff --git a/.jshintignore b/.jshintignore
new file mode 120000
index 0000000..09843c3
--- /dev/null
+++ b/.jshintignore
@@ -0,0 +1 @@
+dev-lib/jshint/.jshintignore
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 120000
index 0000000..a38fb1b
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1 @@
+dev-lib/jshint/.jshintrc
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..947882e
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,84 @@
+# Travis CI configuration file for a WordPoints module.
+
+language: php
+
+# 5.3 runs first so that we can fail fast on the codesniff pass (see below).
+php:
+ - 5.3
+ - 5.2
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+
+env:
+ - TRAVISCI_RUN=codesniff
+ - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=develop
+ - TRAVISCI_RUN=phpunit WP_VERSION=develop WORDPOINTS_VERSION=master
+ - TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=develop
+ - TRAVISCI_RUN=phpunit WP_VERSION=4.7 WORDPOINTS_VERSION=master
+ - TRAVISCI_RUN=phpunit WP_VERSION=4.6 WORDPOINTS_VERSION=develop
+ - TRAVISCI_RUN=phpunit WP_VERSION=4.6 WORDPOINTS_VERSION=master
+
+sudo: false
+
+matrix:
+ include:
+ # Only run nightly against trunk.
+ - php: nightly
+ env: TRAVISCI_RUN=phpunit WP_VERSION=develop
+ exclude:
+ # The codesniff pass only needs to be run once, I chose PHP 5.3, since WPCS requires it.
+ - php: 5.2
+ env: TRAVISCI_RUN=codesniff
+ - php: 5.4
+ env: TRAVISCI_RUN=codesniff
+ - php: 5.5
+ env: TRAVISCI_RUN=codesniff
+ - php: 5.6
+ env: TRAVISCI_RUN=codesniff
+ - php: 7.0
+ env: TRAVISCI_RUN=codesniff
+ - php: 7.1
+ env: TRAVISCI_RUN=codesniff
+ - php: 7.1
+ env: TRAVISCI_RUN=phpunit WP_VERSION=4.6 WORDPOINTS_VERSION=develop
+ - php: 7.1
+ env: TRAVISCI_RUN=phpunit WP_VERSION=4.6 WORDPOINTS_VERSION=master
+ allow_failures:
+ - php: nightly
+ fast_finish: true
+
+before_script:
+ - export WORDPOINTS_PROJECT_TYPE=module
+ - export DEV_LIB_PATH=dev-lib
+ - if [ -e .wordpoints-dev-lib-config.sh ]; then source .wordpoints-dev-lib-config.sh; fi
+ - source $DEV_LIB_PATH/travis/before_script.sh
+
+script:
+ - codesniff-php-syntax
+ - codesniff-php-autoloaders
+ - codesniff-phpcs
+ - codesniff-strings
+ - codesniff-jshint
+ - codesniff-l10n
+ - codesniff-xmllint
+ - codesniff-bash
+ - codesniff-symlinks
+ - phpunit-basic
+ - phpunit-ms
+ - phpunit-ms-network
+ - phpunit-uninstall
+ - phpunit-ms-uninstall
+ - phpunit-ms-network-uninstall
+ - WORDPOINTS_ONLY_UNINSTALL_MODULE=1 phpunit-uninstall
+ - WORDPOINTS_ONLY_UNINSTALL_MODULE=1 phpunit-ms-uninstall
+ - WORDPOINTS_ONLY_UNINSTALL_MODULE=1 phpunit-ms-network-uninstall
+ - phpunit-ajax
+ - phpunit-ms-ajax
+ - phpunit-ms-network-ajax
+ - wpcept-run
+
+after_script:
+ - source $DEV_LIB_PATH/travis/after_script.sh
diff --git a/.wordpoints-dev-lib-config.sh b/.wordpoints-dev-lib-config.sh
new file mode 100644
index 0000000..e08180b
--- /dev/null
+++ b/.wordpoints-dev-lib-config.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+wordpoints-dev-lib-config() {
+
+ CODESNIFF_PHP_AUTOLOADER_DEPENDENCIES+=( \
+ "${WORDPOINTS_DEVELOP_DIR}/src/components/points/classes/" \
+ "${WORDPOINTS_DEVELOP_DIR}/src/components/points/includes/" \
+ "${WORDPOINTS_DEVELOP_DIR}/src/admin/classes/" \
+ )
+
+ export CODESNIFF_PHP_AUTOLOADER_DEPENDENCIES
+}
+
+# EOF
diff --git a/.wp-l10n-validator-cache.json b/.wp-l10n-validator-cache.json
new file mode 100644
index 0000000..6453f2d
--- /dev/null
+++ b/.wp-l10n-validator-cache.json
@@ -0,0 +1,120 @@
+{
+ "files": {
+ "\/classes\/admin\/screen.php": {
+ "size": 5339,
+ "hash": "0a9f21a2910458413471562654d0d147",
+ "errors": false
+ },
+ "\/classes\/block\/logs\/query.php": {
+ "size": 5053,
+ "hash": "7c435cc235b1e8faf4cd4c0bd59fc277",
+ "errors": false
+ },
+ "\/classes\/block\/type\/week\/in\/seconds.php": {
+ "size": 1219,
+ "hash": "57c4dd5fc37d69d53313462dee66f61c",
+ "errors": false
+ },
+ "\/classes\/block\/typei.php": {
+ "size": 826,
+ "hash": "3d59f7c9296496a003afc1de02c031cb",
+ "errors": false
+ },
+ "\/classes\/blocks\/query.php": {
+ "size": 3699,
+ "hash": "e8472ac80f914f71365f2f2685f39d2f",
+ "errors": false
+ },
+ "\/classes\/index.php": {
+ "size": 1393,
+ "hash": "5ccdf1e6196f227a67c0b70b559efc0d",
+ "errors": false
+ },
+ "\/classes\/points\/logs\/query.php": {
+ "size": 2742,
+ "hash": "7a30f04a2bf19206b4fac3fb92551d12",
+ "errors": false
+ },
+ "\/classes\/query\/cache\/transients.php": {
+ "size": 1810,
+ "hash": "b32b76dff25e69a5371639eeb2dc3e6a",
+ "errors": false
+ },
+ "\/classes\/query\/cachei.php": {
+ "size": 775,
+ "hash": "c2b9325eb8700581d9fb828e1c889215",
+ "errors": false
+ },
+ "\/classes\/query.php": {
+ "size": 26160,
+ "hash": "f3d569fc92909930e12c87360dafc492",
+ "errors": false
+ },
+ "\/classes\/table.php": {
+ "size": 6921,
+ "hash": "f59df6c3b260f81bbcdebdb0713b57e7",
+ "errors": false
+ },
+ "\/classes\/widget\/dynamic.php": {
+ "size": 8059,
+ "hash": "df0bd3b5357fa57020808175bb308ce2",
+ "errors": false
+ },
+ "\/classes\/widget\/fixed.php": {
+ "size": 6108,
+ "hash": "bce74c643d9a6596dcd6b3e7e13bd347",
+ "errors": false
+ },
+ "\/includes\/actions.php": {
+ "size": 1254,
+ "hash": "4345301c197fb02e01479a8070997c01",
+ "errors": false
+ },
+ "\/includes\/admin\/actions.php": {
+ "size": 358,
+ "hash": "655592b33fb498686ecbe3420dba9cd7",
+ "errors": false
+ },
+ "\/includes\/admin\/functions.php": {
+ "size": 951,
+ "hash": "572df6255c9c059000990983113b1acf",
+ "errors": false
+ },
+ "\/includes\/class-un-installer.php": {
+ "size": 1186,
+ "hash": "722087a731c12ee81324fbb291552726",
+ "errors": false
+ },
+ "\/includes\/functions.php": {
+ "size": 6235,
+ "hash": "186472fd9afc1d051c7b47bb417e4b7d",
+ "errors": false
+ },
+ "\/top-users-in-period.php": {
+ "size": 2212,
+ "hash": "12242ef034a066344fc1603f2bfa1a8e",
+ "errors": false
+ },
+ "\/classes\/query\/cache\/flusher.php": {
+ "size": 3794,
+ "hash": "0bb816e3607dc7ae4e325875f0913d66",
+ "errors": false
+ },
+ "\/classes\/query\/cache\/index.php": {
+ "size": 2018,
+ "hash": "0a2769f58db2cf1e600a93efc3d218f9",
+ "errors": false
+ },
+ "\/classes\/shortcode\/dynamic.php": {
+ "size": 3869,
+ "hash": "79d4b7b0d4bc917625320c6b3c4be2ca",
+ "errors": false
+ },
+ "\/classes\/shortcode\/fixed.php": {
+ "size": 2337,
+ "hash": "c8992236165f23e66428ec133d0fad0d",
+ "errors": false
+ }
+ },
+ "config_signature": "d1f4c65c727b0519408cbf01cf91baaa"
+}
\ No newline at end of file
diff --git a/.wp-l10n-validator-ignores-cache.json b/.wp-l10n-validator-ignores-cache.json
new file mode 100644
index 0000000..a8f4a84
--- /dev/null
+++ b/.wp-l10n-validator-ignores-cache.json
@@ -0,0 +1,476 @@
+{
+ "\/classes\/admin\/screen.php": {
+ "-1": {
+ "166": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 4,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/classes\/block\/logs\/query.php": {
+ "total": {
+ "85": false,
+ "115": {
+ "name": "WordPoints_Top_Users_In_Period_Block_Logs_Query::prepare_column_where",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/classes\/blocks\/query.php": {
+ "draft": {
+ "33": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "filled": {
+ "33": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 1,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/classes\/points\/logs\/query.php": {
+ "total": {
+ "50": false,
+ "83": {
+ "name": "WordPoints_Top_Users_In_Period_Points_Logs_Query::prepare_column_where",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/classes\/query\/cache\/transients.php": {
+ "=": {
+ "75": false
+ }
+ },
+ "\/classes\/query.php": {
+ "U": {
+ "135": {
+ "name": "WordPoints_Top_Users_In_Period_Query::start_date->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "136": {
+ "name": "WordPoints_Top_Users_In_Period_Query::end_date->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "=": {
+ "207": false
+ },
+ "total": {
+ "340": {
+ "name": "WordPoints_Top_Users_In_Period_Query::prepare_column_where",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "751": false
+ },
+ "transients": {
+ "394": false,
+ "425": false
+ },
+ "!=": {
+ "519": false
+ },
+ ") AS `t`": {
+ "717": false
+ },
+ "limit": {
+ "747": false
+ },
+ "start": {
+ "748": false
+ },
+ "order": {
+ "749": false
+ },
+ "OR": {
+ "799": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "draft": {
+ "907": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/classes\/table.php": {
+ "striped": {
+ "278": false
+ }
+ },
+ "\/includes\/admin\/functions.php": {
+ "display": {
+ "34": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 1,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/includes\/class-un-installer.php": {
+ "WordPoints_Top_Users_In_Period_Un_Installer": {
+ "50": false
+ }
+ },
+ "\/includes\/functions.php": {
+ "widget-settings": {
+ "56": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 1,
+ "parentheses": 1
+ }
+ },
+ "wordpoints-top-users-in-period-": {
+ "60": false
+ },
+ "rtl": {
+ "62": {
+ "name": "$styles->add_data",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 1,
+ "parentheses": 1
+ }
+ },
+ "replace": {
+ "62": {
+ "name": "$styles->add_data",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 2,
+ "parentheses": 1
+ }
+ },
+ "suffix": {
+ "65": {
+ "name": "$styles->add_data",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 1,
+ "parentheses": 1
+ }
+ },
+ "UTC": {
+ "109": false,
+ "123": {
+ "name": "DateTimeZone::__construct",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "%+03d:%02d": {
+ "115": {
+ "name": "sprintf",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ }
+ },
+ "\/classes\/widget\/dynamic.php": {
+ "present": {
+ "41": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 4,
+ "parentheses": 1
+ }
+ },
+ "days": {
+ "42": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 5,
+ "parentheses": 1
+ },
+ "242": false
+ },
+ "H": {
+ "108": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "112": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "142": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "146": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "i": {
+ "108": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "142": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "-": {
+ "138": {
+ "name": "$from->modify",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "Y": {
+ "156": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "177": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "m": {
+ "157": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "w": {
+ "167": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "W": {
+ "178": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "seconds": {
+ "231": false
+ },
+ "\"\n\t\t\t\t\t\tclass=\"widefat wordpoints-top-users-in-period-units\"\n\t\t\t\t\t>": {
+ "231": false
+ },
+ "minutes": {
+ "236": false
+ },
+ "hours": {
+ "239": false
+ },
+ "weeks": {
+ "245": false
+ },
+ "months": {
+ "248": false
+ },
+ "\"\n\t\t\t\t\t\t\ttype=\"radio\"\n\t\t\t\t\t\t\tvalue=\"present\"": {
+ "269": false
+ },
+ "class=\"widefat\"\n\t\t\t\t\t\t\/>": {
+ "273": false,
+ "285": false
+ },
+ "\"\n\t\t\t\t\t\t\ttype=\"radio\"\n\t\t\t\t\t\t\tvalue=\"calendar\"": {
+ "281": false
+ }
+ },
+ "\/classes\/shortcode\/dynamic.php": {
+ "days": {
+ "25": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 3,
+ "parentheses": 1
+ }
+ },
+ "present": {
+ "26": {
+ "name": "array",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 4,
+ "parentheses": 1
+ }
+ },
+ "H": {
+ "75": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "79": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "109": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "113": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "i": {
+ "75": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "109": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "-": {
+ "105": {
+ "name": "$from->modify",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "Y": {
+ "123": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ },
+ "144": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "m": {
+ "124": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "w": {
+ "134": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ },
+ "W": {
+ "145": {
+ "name": "$from->format",
+ "type": "unknown",
+ "args_started": true,
+ "arg_count": 0,
+ "parentheses": 1
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..844f1d8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,31 @@
+# Change Log for Top Users In Period
+
+All notable changes to this project will be documented in this file.
+
+This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a CHANGELOG](http://keepachangelog.com/).
+
+## [1.0.0] - 2017-05-08
+
+### Added
+
+- Query class to query the total points for each user in a period. #4
+ - Uses a points logs query class for short periods.
+ - Uses a query including blocks of data cached in the database for longer periods.
+ - Automatically caches the data in the blocks on the fly.
+ - Uses blocks that are one week in seconds long by default.
+ - Uses a block types app to manage the block types.
+ - Also caches the results of finished queries.
+ - Uses a query cache types app to manage the query caches.
+ - Caches query results in transients by default.
+- Cache invalidation handlers that automatically clear the caches for open-ended
+ queries as needed whenever a new points transaction occurs.
+- Class to display a query's results in a table.
+- Admin screen where admins can query the totals over fixed periods.
+- Widget that displays totals from a fixed period.
+- Widget that displays totals from a dynamic period.
+ - Period can be calculated relative to the calendar, or relative to the present.
+- Shortcode that displays totals from a fixed period.
+- Shortcode that displays totals from a dynamic period.
+
+[unreleased]: https://github.com/WordPoints/top-users-in-period/compare/master...HEAD
+[1.0.0]: https://github.com/WordPoints/top-users-in-period/compare/...1.0.0
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..1b773dc
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,15 @@
+/**
+ * Grunt configuration file.
+ *
+ * @package WordPoints_Dev_Lib
+ * @since 2.5.0
+ */
+
+/* jshint node:true */
+module.exports = function( grunt ) {
+
+ // Load the default configuration from the dev-lib.
+ require( './dev-lib/grunt/modules/configure.js' )( grunt, __dirname );
+};
+
+// EOF
diff --git a/LICENSE b/LICENSE
index 23cb790..c5ddbfb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
diff --git a/README.md b/README.md
index b0d508e..27a9d45 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,15 @@
-# top-users-in-period
-Display the top points earners within a given period of time
+# Top Users in Period [![Build Status](https://travis-ci.org/WordPoints/top-users-in-period.svg?branch=develop)](https://travis-ci.org/WordPoints/top-users-in-period) [![HackerOne Bug Bounty Program](https://img.shields.io/badge/security-HackerOne-blue.svg)](https://hackerone.com/wordpoints)
+
+Display the top points earners within a given period of time.
+
+---
+
+Hello! This is where we develop the premium Top Users in Period module that you
+can [purchase on WordPoints.org](https://wordpoints.org/modules/top-users-in-period/).
+The source code of the module is made available here in good faith, to make it easier
+for developers to integrate with it, contribute bugfixes and security patches, and
+follow the development of new features. You can also feel free to use this source
+code to try the module out before you buy it. However, if you decide to use the
+module on a production site, we ask that you [purchase a license for it](https://wordpoints.org/modules/top-users-in-period/),
+so that we can continue to provide updates and support for WordPoints and all of its
+great modules. Thank you!
diff --git a/codeception.dist.yml b/codeception.dist.yml
new file mode 120000
index 0000000..7387259
--- /dev/null
+++ b/codeception.dist.yml
@@ -0,0 +1 @@
+dev-lib/wpcept/module.yml
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..252a49d
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,20 @@
+{
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "jdgrimes/wpppb": "^0.2",
+ "xrstf/composer-php52": "^1.0"
+ },
+ "scripts": {
+ "post-install-cmd": [
+ "xrstf\\Composer52\\Generator::onPostInstallCmd"
+ ],
+ "post-update-cmd": [
+ "xrstf\\Composer52\\Generator::onPostInstallCmd"
+ ],
+ "post-autoload-dump": [
+ "xrstf\\Composer52\\Generator::onPostInstallCmd"
+ ]
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..805288d
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,93 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "ca19a28eefc80ca71fec656a507e8a46",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "jdgrimes/wpppb",
+ "version": "0.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/JDGrimes/wpppb.git",
+ "reference": "2574c5a3fc700a30cfcec2f577c4983a4f49409a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/JDGrimes/wpppb/zipball/2574c5a3fc700a30cfcec2f577c4983a4f49409a",
+ "reference": "2574c5a3fc700a30cfcec2f577c4983a4f49409a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "bin": [
+ "bin/wpppb-init"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "WPPPB_": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "J.D. Grimes",
+ "email": "jdg@codesymphony.co",
+ "homepage": "http://codesymphony.co",
+ "role": "Developer"
+ }
+ ],
+ "description": "Bootstrap for integration testing WordPress plugins with PHPUnit",
+ "homepage": "https://github.com/JDGrimes/wpppb",
+ "time": "2017-01-17T15:02:34+00:00"
+ },
+ {
+ "name": "xrstf/composer-php52",
+ "version": "v1.0.20",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer-php52/composer-php52.git",
+ "reference": "bd41459d5e27df8d33057842b32377c39e97a5a8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer-php52/composer-php52/zipball/bd41459d5e27df8d33057842b32377c39e97a5a8",
+ "reference": "bd41459d5e27df8d33057842b32377c39e97a5a8",
+ "shasum": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-default": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "xrstf\\Composer52": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "time": "2016-04-16T21:52:24+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.2.0"
+ },
+ "platform-dev": []
+}
diff --git a/dev-lib b/dev-lib
new file mode 160000
index 0000000..dd49b98
--- /dev/null
+++ b/dev-lib
@@ -0,0 +1 @@
+Subproject commit dd49b98c20b7cea9e4881606a05c4169253d6720
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..dc8550c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "devDependencies": {
+ "@martin-pettersson/wp-get-file-data": "^0.1.0",
+ "autoprefixer": "^6.6.1",
+ "grunt": "^1.0.0",
+ "grunt-browserify": "^5.0.0",
+ "grunt-contrib-cssmin": "^1.0.2",
+ "grunt-contrib-imagemin": "^1.0.1",
+ "grunt-contrib-uglify": "^2.0.0",
+ "grunt-contrib-watch": "^1.0.0",
+ "grunt-jsvalidate": "^0.2.2",
+ "grunt-newer": "^1.2.0",
+ "grunt-openport": "^1.0.0",
+ "grunt-postcss": "^0.8.0",
+ "grunt-rtlcss": "^2.0.1",
+ "grunt-sass": "~1.1.0",
+ "matchdep": "^1.0.1"
+ }
+}
diff --git a/phpcs.ruleset.xml b/phpcs.ruleset.xml
new file mode 120000
index 0000000..a792581
--- /dev/null
+++ b/phpcs.ruleset.xml
@@ -0,0 +1 @@
+dev-lib/phpcs/WordPoints/ruleset.xml
\ No newline at end of file
diff --git a/phpunit.uninstall.xml.dist b/phpunit.uninstall.xml.dist
new file mode 100755
index 0000000..6a0b118
--- /dev/null
+++ b/phpunit.uninstall.xml.dist
@@ -0,0 +1,16 @@
+
+
+
+ tests/phpunit/test-uninstall.php
+
+
+
+
+ src/
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 120000
index 0000000..fb78a1e
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1 @@
+dev-lib/phpunit/module.xml.dist
\ No newline at end of file
diff --git a/src/assets/css/images/ui-icons_444444_256x240.png b/src/assets/css/images/ui-icons_444444_256x240.png
new file mode 100644
index 0000000..19f664d
Binary files /dev/null and b/src/assets/css/images/ui-icons_444444_256x240.png differ
diff --git a/src/assets/css/images/ui-icons_555555_256x240.png b/src/assets/css/images/ui-icons_555555_256x240.png
new file mode 100644
index 0000000..e965f6d
Binary files /dev/null and b/src/assets/css/images/ui-icons_555555_256x240.png differ
diff --git a/src/assets/css/images/ui-icons_777620_256x240.png b/src/assets/css/images/ui-icons_777620_256x240.png
new file mode 100644
index 0000000..9785948
Binary files /dev/null and b/src/assets/css/images/ui-icons_777620_256x240.png differ
diff --git a/src/assets/css/images/ui-icons_777777_256x240.png b/src/assets/css/images/ui-icons_777777_256x240.png
new file mode 100644
index 0000000..323c456
Binary files /dev/null and b/src/assets/css/images/ui-icons_777777_256x240.png differ
diff --git a/src/assets/css/images/ui-icons_cc0000_256x240.png b/src/assets/css/images/ui-icons_cc0000_256x240.png
new file mode 100644
index 0000000..45ac778
Binary files /dev/null and b/src/assets/css/images/ui-icons_cc0000_256x240.png differ
diff --git a/src/assets/css/images/ui-icons_ffffff_256x240.png b/src/assets/css/images/ui-icons_ffffff_256x240.png
new file mode 100644
index 0000000..fe41d2d
Binary files /dev/null and b/src/assets/css/images/ui-icons_ffffff_256x240.png differ
diff --git a/src/assets/css/jquery-ui-datepicker.css b/src/assets/css/jquery-ui-datepicker.css
new file mode 100644
index 0000000..e98f841
--- /dev/null
+++ b/src/assets/css/jquery-ui-datepicker.css
@@ -0,0 +1,702 @@
+/*! jQuery UI - v1.12.1 - 2016-09-21
+* https://jqueryui.com
+* Includes: core.css, datepicker.css, theme.css
+* To view and modify this theme, visit https://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden {
+ display: none;
+}
+.ui-helper-hidden-accessible {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+.ui-helper-reset {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ line-height: 1.3;
+ text-decoration: none;
+ font-size: 100%;
+ list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+ content: "";
+ display: table;
+ border-collapse: collapse;
+}
+.ui-helper-clearfix:after {
+ clear: both;
+}
+.ui-helper-zfix {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ position: absolute;
+ opacity: 0;
+ filter:Alpha(Opacity=0); /* support: IE8 */
+}
+
+.ui-front {
+ z-index: 100;
+}
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled {
+ cursor: default !important;
+ pointer-events: none;
+}
+
+
+/* Icons
+----------------------------------*/
+.ui-icon {
+ display: inline-block;
+ vertical-align: middle;
+ margin-top: -.25em;
+ position: relative;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+}
+
+.ui-widget-icon-block {
+ left: 50%;
+ margin-left: -8px;
+ display: block;
+}
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.ui-datepicker {
+ width: 17em;
+ padding: .2em .2em 0;
+ display: none;
+}
+.ui-datepicker .ui-datepicker-header {
+ position: relative;
+ padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+ position: absolute;
+ top: 2px;
+ width: 1.8em;
+ height: 1.8em;
+}
+.ui-datepicker .ui-datepicker-prev-hover,
+.ui-datepicker .ui-datepicker-next-hover {
+ top: 1px;
+}
+.ui-datepicker .ui-datepicker-prev {
+ left: 2px;
+}
+.ui-datepicker .ui-datepicker-next {
+ right: 2px;
+}
+.ui-datepicker .ui-datepicker-prev-hover {
+ left: 1px;
+}
+.ui-datepicker .ui-datepicker-next-hover {
+ right: 1px;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+ display: block;
+ position: absolute;
+ left: 50%;
+ margin-left: -8px;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-datepicker .ui-datepicker-title {
+ margin: 0 2.3em;
+ line-height: 1.8em;
+ text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+ font-size: 1em;
+ margin: 1px 0;
+}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+ width: 45%;
+}
+.ui-datepicker table {
+ width: 100%;
+ font-size: .9em;
+ border-collapse: collapse;
+ margin: 0 0 .4em;
+}
+.ui-datepicker th {
+ padding: .7em .3em;
+ text-align: center;
+ font-weight: bold;
+ border: 0;
+}
+.ui-datepicker td {
+ border: 0;
+ padding: 1px;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+ display: block;
+ padding: .2em;
+ text-align: right;
+ text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+ background-image: none;
+ margin: .7em 0 0 0;
+ padding: 0 .2em;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+ float: right;
+ margin: .5em .2em .4em;
+ cursor: pointer;
+ padding: .2em .6em .3em .6em;
+ width: auto;
+ overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+ float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+ width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+ float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+ width: 95%;
+ margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+ width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+ width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+ width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+ border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+ clear: left;
+}
+.ui-datepicker-row-break {
+ clear: both;
+ width: 100%;
+ font-size: 0;
+}
+
+/* RTL support */
+.ui-datepicker-rtl {
+ direction: rtl;
+}
+.ui-datepicker-rtl .ui-datepicker-prev {
+ right: 2px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next {
+ left: 2px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-prev:hover {
+ right: 1px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next:hover {
+ left: 1px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane {
+ clear: right;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button {
+ float: left;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
+.ui-datepicker-rtl .ui-datepicker-group {
+ float: right;
+}
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
+ border-right-width: 0;
+ border-left-width: 1px;
+}
+
+/* Icons */
+.ui-datepicker .ui-icon {
+ display: block;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+ left: .5em;
+ top: .3em;
+}
+
+/* Component containers
+----------------------------------*/
+.ui-widget {
+ font-family: Arial,Helvetica,sans-serif;
+ font-size: 1em;
+}
+.ui-widget .ui-widget {
+ font-size: 1em;
+}
+.ui-widget input,
+.ui-widget select,
+.ui-widget textarea,
+.ui-widget button {
+ font-family: Arial,Helvetica,sans-serif;
+ font-size: 1em;
+}
+.ui-widget.ui-widget-content {
+ border: 1px solid #c5c5c5;
+}
+.ui-widget-content {
+ border: 1px solid #dddddd;
+ background: #ffffff;
+ color: #333333;
+}
+.ui-widget-content a {
+ color: #333333;
+}
+.ui-widget-header {
+ border: 1px solid #dddddd;
+ background: #e9e9e9;
+ color: #333333;
+ font-weight: bold;
+}
+.ui-widget-header a {
+ color: #333333;
+}
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default,
+.ui-widget-content .ui-state-default,
+.ui-widget-header .ui-state-default,
+.ui-button,
+
+/* We use html here because we need a greater specificity to make sure disabled
+works properly when clicked or hovered */
+html .ui-button.ui-state-disabled:hover,
+html .ui-button.ui-state-disabled:active {
+ border: 1px solid #c5c5c5;
+ background: #f6f6f6;
+ font-weight: normal;
+ color: #454545;
+}
+.ui-state-default a,
+.ui-state-default a:link,
+.ui-state-default a:visited,
+a.ui-button,
+a:link.ui-button,
+a:visited.ui-button,
+.ui-button {
+ color: #454545;
+ text-decoration: none;
+}
+.ui-state-hover,
+.ui-widget-content .ui-state-hover,
+.ui-widget-header .ui-state-hover,
+.ui-state-focus,
+.ui-widget-content .ui-state-focus,
+.ui-widget-header .ui-state-focus,
+.ui-button:hover,
+.ui-button:focus {
+ border: 1px solid #cccccc;
+ background: #ededed;
+ font-weight: normal;
+ color: #2b2b2b;
+}
+.ui-state-hover a,
+.ui-state-hover a:hover,
+.ui-state-hover a:link,
+.ui-state-hover a:visited,
+.ui-state-focus a,
+.ui-state-focus a:hover,
+.ui-state-focus a:link,
+.ui-state-focus a:visited,
+a.ui-button:hover,
+a.ui-button:focus {
+ color: #2b2b2b;
+ text-decoration: none;
+}
+
+.ui-visual-focus {
+ -webkit-box-shadow: 0 0 3px 1px rgb(94, 158, 214);
+ box-shadow: 0 0 3px 1px rgb(94, 158, 214);
+}
+.ui-state-active,
+.ui-widget-content .ui-state-active,
+.ui-widget-header .ui-state-active,
+a.ui-button:active,
+.ui-button:active,
+.ui-button.ui-state-active:hover {
+ border: 1px solid #003eff;
+ background: #007fff;
+ font-weight: normal;
+ color: #ffffff;
+}
+.ui-icon-background,
+.ui-state-active .ui-icon-background {
+ border: #003eff;
+ background-color: #ffffff;
+}
+.ui-state-active a,
+.ui-state-active a:link,
+.ui-state-active a:visited {
+ color: #ffffff;
+ text-decoration: none;
+}
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight,
+.ui-widget-content .ui-state-highlight,
+.ui-widget-header .ui-state-highlight {
+ border: 1px solid #dad55e;
+ background: #fffa90;
+ color: #777620;
+}
+.ui-state-checked {
+ border: 1px solid #dad55e;
+ background: #fffa90;
+}
+.ui-state-highlight a,
+.ui-widget-content .ui-state-highlight a,
+.ui-widget-header .ui-state-highlight a {
+ color: #777620;
+}
+.ui-state-error,
+.ui-widget-content .ui-state-error,
+.ui-widget-header .ui-state-error {
+ border: 1px solid #f1a899;
+ background: #fddfdf;
+ color: #5f3f3f;
+}
+.ui-state-error a,
+.ui-widget-content .ui-state-error a,
+.ui-widget-header .ui-state-error a {
+ color: #5f3f3f;
+}
+.ui-state-error-text,
+.ui-widget-content .ui-state-error-text,
+.ui-widget-header .ui-state-error-text {
+ color: #5f3f3f;
+}
+.ui-priority-primary,
+.ui-widget-content .ui-priority-primary,
+.ui-widget-header .ui-priority-primary {
+ font-weight: bold;
+}
+.ui-priority-secondary,
+.ui-widget-content .ui-priority-secondary,
+.ui-widget-header .ui-priority-secondary {
+ opacity: .7;
+ filter:Alpha(Opacity=70); /* support: IE8 */
+ font-weight: normal;
+}
+.ui-state-disabled,
+.ui-widget-content .ui-state-disabled,
+.ui-widget-header .ui-state-disabled {
+ opacity: .35;
+ filter:Alpha(Opacity=35); /* support: IE8 */
+ background-image: none;
+}
+.ui-state-disabled .ui-icon {
+ filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
+}
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+ width: 16px;
+ height: 16px;
+}
+.ui-icon,
+.ui-widget-content .ui-icon {
+ background-image: url("images/ui-icons_444444_256x240.png");
+}
+.ui-widget-header .ui-icon {
+ background-image: url("images/ui-icons_444444_256x240.png");
+}
+.ui-state-hover .ui-icon,
+.ui-state-focus .ui-icon,
+.ui-button:hover .ui-icon,
+.ui-button:focus .ui-icon {
+ background-image: url("images/ui-icons_555555_256x240.png");
+}
+.ui-state-active .ui-icon,
+.ui-button:active .ui-icon {
+ background-image: url("images/ui-icons_ffffff_256x240.png");
+}
+.ui-state-highlight .ui-icon,
+.ui-button .ui-state-highlight.ui-icon {
+ background-image: url("images/ui-icons_777620_256x240.png");
+}
+.ui-state-error .ui-icon,
+.ui-state-error-text .ui-icon {
+ background-image: url("images/ui-icons_cc0000_256x240.png");
+}
+.ui-button .ui-icon {
+ background-image: url("images/ui-icons_777777_256x240.png");
+}
+
+/* positioning */
+.ui-icon-blank { background-position: 16px 16px; }
+.ui-icon-caret-1-n { background-position: 0 0; }
+.ui-icon-caret-1-ne { background-position: -16px 0; }
+.ui-icon-caret-1-e { background-position: -32px 0; }
+.ui-icon-caret-1-se { background-position: -48px 0; }
+.ui-icon-caret-1-s { background-position: -65px 0; }
+.ui-icon-caret-1-sw { background-position: -80px 0; }
+.ui-icon-caret-1-w { background-position: -96px 0; }
+.ui-icon-caret-1-nw { background-position: -112px 0; }
+.ui-icon-caret-2-n-s { background-position: -128px 0; }
+.ui-icon-caret-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -65px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -65px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 1px -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-left,
+.ui-corner-tl {
+ -webkit-border-top-left-radius: 3px;
+ border-top-left-radius: 3px;
+}
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-right,
+.ui-corner-tr {
+ -webkit-border-top-right-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-left,
+.ui-corner-bl {
+ -webkit-border-bottom-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-right,
+.ui-corner-br {
+ -webkit-border-bottom-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+
+/* Overlays */
+.ui-widget-overlay {
+ background: #aaaaaa;
+ opacity: .3;
+ filter: Alpha(Opacity=30); /* support: IE8 */
+}
+.ui-widget-shadow {
+ -webkit-box-shadow: 0px 0px 5px #666666;
+ box-shadow: 0px 0px 5px #666666;
+}
diff --git a/src/assets/css/jquery-ui-datepicker.min.css b/src/assets/css/jquery-ui-datepicker.min.css
new file mode 100644
index 0000000..53a1b30
--- /dev/null
+++ b/src/assets/css/jquery-ui-datepicker.min.css
@@ -0,0 +1,5 @@
+/*! jQuery UI - v1.12.1 - 2016-09-21
+* https://jqueryui.com
+* Includes: core.css, datepicker.css, theme.css
+* To view and modify this theme, visit https://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
+* Copyright jQuery Foundation and other contributors; Licensed MIT */.ui-datepicker .ui-icon,.ui-icon{text-indent:-99999px;background-repeat:no-repeat}.ui-widget-content a,.ui-widget-header,.ui-widget-header a{color:#333}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:after,.ui-helper-clearfix:before{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;overflow:hidden}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-next,.ui-datepicker .ui-datepicker-prev{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-next-hover,.ui-datepicker .ui-datepicker-prev-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-next span,.ui-datepicker .ui-datepicker-prev span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:700;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td a,.ui-datepicker td span{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-multi .ui-datepicker-group,.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-widget,.ui-widget .ui-widget{font-size:1em}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;overflow:hidden;left:.5em;top:.3em}.ui-widget{font-family:Arial,Helvetica,sans-serif}.ui-widget button,.ui-widget input,.ui-widget select,.ui-widget textarea{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;font-weight:700}.ui-button,.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,html .ui-button.ui-state-disabled:active,html .ui-button.ui-state-disabled:hover{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:400;color:#454545}.ui-button,.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button{color:#454545;text-decoration:none}.ui-button:focus,.ui-button:hover,.ui-state-focus,.ui-state-hover,.ui-widget-content .ui-state-focus,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-focus,.ui-widget-header .ui-state-hover{border:1px solid #ccc;background:#ededed;font-weight:400;color:#2b2b2b}.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,a.ui-button:focus,a.ui-button:hover{color:#2b2b2b;text-decoration:none}.ui-visual-focus{-webkit-box-shadow:0 0 3px 1px #5e9ed6;box-shadow:0 0 3px 1px #5e9ed6}.ui-button.ui-state-active:hover,.ui-button:active,.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active{border:1px solid #003eff;background:#007fff;font-weight:400;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-state-error-text,.ui-widget-content .ui-state-error a,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error a,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:700}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:400}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon,.ui-widget-header .ui-icon{background-image:url(images/ui-icons_444444_256x240.png)}.ui-button:focus .ui-icon,.ui-button:hover .ui-icon,.ui-state-focus .ui-icon,.ui-state-hover .ui-icon{background-image:url(images/ui-icons_555555_256x240.png)}.ui-button:active .ui-icon,.ui-state-active .ui-icon{background-image:url(images/ui-icons_ffffff_256x240.png)}.ui-button .ui-state-highlight.ui-icon,.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_777620_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cc0000_256x240.png)}.ui-button .ui-icon{background-image:url(images/ui-icons_777777_256x240.png)}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-first,.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-left,.ui-corner-tl,.ui-corner-top{-webkit-border-top-left-radius:3px;border-top-left-radius:3px}.ui-corner-all,.ui-corner-right,.ui-corner-top,.ui-corner-tr{-webkit-border-top-right-radius:3px;border-top-right-radius:3px}.ui-corner-all,.ui-corner-bl,.ui-corner-bottom,.ui-corner-left{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-br,.ui-corner-right{-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666}
\ No newline at end of file
diff --git a/src/assets/css/table-rtl.css b/src/assets/css/table-rtl.css
new file mode 100644
index 0000000..ad54c8e
--- /dev/null
+++ b/src/assets/css/table-rtl.css
@@ -0,0 +1,18 @@
+/**
+ * Styles for the top users in period table.
+ *
+ * @package WordPoints_Top_Users_In_Period
+ * @since 1.0.0
+ */
+
+.wordpoints-top-users-in-period {
+ table-layout: auto;
+}
+
+.wordpoints-top-users-in-period .avatar {
+ vertical-align: middle;
+ display: inline-block;
+ margin-left: 10px;
+}
+
+/* EOF */
diff --git a/src/assets/css/table-rtl.min.css b/src/assets/css/table-rtl.min.css
new file mode 100644
index 0000000..5b7fa43
--- /dev/null
+++ b/src/assets/css/table-rtl.min.css
@@ -0,0 +1 @@
+.wordpoints-top-users-in-period{table-layout:auto}.wordpoints-top-users-in-period .avatar{vertical-align:middle;display:inline-block;margin-left:10px}
\ No newline at end of file
diff --git a/src/assets/css/table.css b/src/assets/css/table.css
new file mode 100644
index 0000000..01ab935
--- /dev/null
+++ b/src/assets/css/table.css
@@ -0,0 +1,18 @@
+/**
+ * Styles for the top users in period table.
+ *
+ * @package WordPoints_Top_Users_In_Period
+ * @since 1.0.0
+ */
+
+.wordpoints-top-users-in-period {
+ table-layout: auto;
+}
+
+.wordpoints-top-users-in-period .avatar {
+ vertical-align: middle;
+ display: inline-block;
+ margin-right: 10px;
+}
+
+/* EOF */
diff --git a/src/assets/css/table.min.css b/src/assets/css/table.min.css
new file mode 100644
index 0000000..610a4c9
--- /dev/null
+++ b/src/assets/css/table.min.css
@@ -0,0 +1 @@
+.wordpoints-top-users-in-period{table-layout:auto}.wordpoints-top-users-in-period .avatar{vertical-align:middle;display:inline-block;margin-right:10px}
\ No newline at end of file
diff --git a/src/assets/css/widget-settings-rtl.css b/src/assets/css/widget-settings-rtl.css
new file mode 100644
index 0000000..19d9108
--- /dev/null
+++ b/src/assets/css/widget-settings-rtl.css
@@ -0,0 +1,30 @@
+/**
+ * Styles for the top users in period widget settings.
+ *
+ * @package WordPoints_Top_Users_In_Period
+ * @since 1.0.0
+ */
+
+/* Necessary because of .wp-full-overlay. */
+.ui-datepicker {
+ z-index: 500000 !important;
+}
+
+input.wordpoints-top-users-in-period-length-in-units[type=number] {
+ width: 48%;
+}
+
+select.wordpoints-top-users-in-period-units {
+ width: 50%;
+ float: left;
+}
+
+.wordpoints-top-users-in-period-form input[type=date] {
+ width: 70%;
+}
+
+.wordpoints-top-users-in-period-form input[type=time] {
+ width: 28%;
+}
+
+/* EOF */
diff --git a/src/assets/css/widget-settings-rtl.min.css b/src/assets/css/widget-settings-rtl.min.css
new file mode 100644
index 0000000..a4376f9
--- /dev/null
+++ b/src/assets/css/widget-settings-rtl.min.css
@@ -0,0 +1 @@
+.ui-datepicker{z-index:500000!important}input.wordpoints-top-users-in-period-length-in-units[type=number]{width:48%}select.wordpoints-top-users-in-period-units{width:50%;float:left}.wordpoints-top-users-in-period-form input[type=date]{width:70%}.wordpoints-top-users-in-period-form input[type=time]{width:28%}
\ No newline at end of file
diff --git a/src/assets/css/widget-settings.css b/src/assets/css/widget-settings.css
new file mode 100644
index 0000000..096e738
--- /dev/null
+++ b/src/assets/css/widget-settings.css
@@ -0,0 +1,30 @@
+/**
+ * Styles for the top users in period widget settings.
+ *
+ * @package WordPoints_Top_Users_In_Period
+ * @since 1.0.0
+ */
+
+/* Necessary because of .wp-full-overlay. */
+.ui-datepicker {
+ z-index: 500000 !important;
+}
+
+input.wordpoints-top-users-in-period-length-in-units[type=number] {
+ width: 48%;
+}
+
+select.wordpoints-top-users-in-period-units {
+ width: 50%;
+ float: right;
+}
+
+.wordpoints-top-users-in-period-form input[type=date] {
+ width: 70%;
+}
+
+.wordpoints-top-users-in-period-form input[type=time] {
+ width: 28%;
+}
+
+/* EOF */
diff --git a/src/assets/css/widget-settings.min.css b/src/assets/css/widget-settings.min.css
new file mode 100644
index 0000000..549aafd
--- /dev/null
+++ b/src/assets/css/widget-settings.min.css
@@ -0,0 +1 @@
+.ui-datepicker{z-index:500000!important}input.wordpoints-top-users-in-period-length-in-units[type=number]{width:48%}select.wordpoints-top-users-in-period-units{width:50%;float:right}.wordpoints-top-users-in-period-form input[type=date]{width:70%}.wordpoints-top-users-in-period-form input[type=time]{width:28%}
\ No newline at end of file
diff --git a/src/assets/js/datepicker.js b/src/assets/js/datepicker.js
new file mode 100644
index 0000000..1d9508f
--- /dev/null
+++ b/src/assets/js/datepicker.js
@@ -0,0 +1,71 @@
+/**
+ * Adds date-picker support to the admin screen.
+ *
+ * @package WordPoints_Top_Users_In_Period
+ * @since 1.0.0
+ */
+
+jQuery( function( $ ) {
+
+ var $form = $( '.wordpoints-top-users-in-period-form' );
+
+ function getDate( element ) {
+
+ var date;
+
+ try {
+ date = $.datepicker.parseDate( 'yy-mm-dd', element.value );
+ } catch ( error ) {
+ date = null;
+ }
+
+ return date;
+ }
+
+ function initDatePicker ( el ) {
+
+ var $el = $( el );
+
+ var $from = $el.find( '.wordpoints-top-users-in-period-from' )
+ .datepicker( {
+ showOtherMonths: true,
+ selectOtherMonths: true,
+ dateFormat: 'yy-mm-dd',
+ maxDate: 0
+ } )
+ .on( 'change', function () {
+ $to.datepicker( 'option', 'minDate', getDate( this ) );
+ } );
+
+ var $to = $el.find( '.wordpoints-top-users-in-period-to' )
+ .datepicker( {
+ showOtherMonths: true,
+ selectOtherMonths: true,
+ dateFormat: 'yy-mm-dd'
+ } )
+ .on( 'change', function () {
+ $from.datepicker( 'option', 'maxDate', getDate( this ) );
+ } );
+
+ $from.change();
+ $to.change();
+ }
+
+ $form.each( initDatePicker );
+
+ $( '.wrap' ).on(
+ 'focus'
+ , '.wordpoints-top-users-in-period-from, .wordpoints-top-users-in-period-to'
+ , function() {
+
+ var $this = $( this );
+
+ if ( ! $this.hasClass( 'hasDatepicker' ) ) {
+ initDatePicker( $this.closest( '.wordpoints-top-users-in-period-form' ) );
+ }
+ }
+ );
+
+} );
+
+// EOF
diff --git a/src/assets/js/datepicker.min.js b/src/assets/js/datepicker.min.js
new file mode 100644
index 0000000..328cb60
--- /dev/null
+++ b/src/assets/js/datepicker.min.js
@@ -0,0 +1 @@
+jQuery(function(a){function b(b){var c;try{c=a.datepicker.parseDate("yy-mm-dd",b.value)}catch(d){c=null}return c}function c(c){var d=a(c),e=d.find(".wordpoints-top-users-in-period-from").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",maxDate:0}).on("change",function(){f.datepicker("option","minDate",b(this))}),f=d.find(".wordpoints-top-users-in-period-to").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd"}).on("change",function(){e.datepicker("option","maxDate",b(this))});e.change(),f.change()}a(".wordpoints-top-users-in-period-form").each(c),a(".wrap").on("focus",".wordpoints-top-users-in-period-from, .wordpoints-top-users-in-period-to",function(){var b=a(this);b.hasClass("hasDatepicker")||c(b.closest(".wordpoints-top-users-in-period-form"))})});
\ No newline at end of file
diff --git a/src/classes/admin/screen.php b/src/classes/admin/screen.php
new file mode 100644
index 0000000..6dd4ab1
--- /dev/null
+++ b/src/classes/admin/screen.php
@@ -0,0 +1,193 @@
+get_search_args();
+
+ ?>
+
+
+
+ display_query_results( $args );
+ }
+
+ /**
+ * Displays the results of the query submitted by the user.
+ *
+ * @since 1.0.0
+ *
+ * @param array $data The user-submitted data.
+ */
+ protected function display_query_results( $data ) {
+
+ if ( ! wordpoints_verify_nonce( 'nonce', 'wordpoints_top_users_in_period_admin_query' ) ) {
+ return;
+ }
+
+ if ( empty( $data['from'] ) ) {
+ return;
+ }
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $from = new DateTime( "{$data['from']} {$data['from_time']}:00", $timezone );
+
+ $to = null;
+
+ if ( ! empty( $data['to'] ) ) {
+ $to = new DateTime( "{$data['to']} {$data['to_time']}:59", $timezone );
+ }
+
+ $args = array();
+
+ if ( ! empty( $data['points_type'] ) ) {
+ $args['points_type'] = $data['points_type'];
+ }
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $from, $to, $args );
+
+ $table = new WordPoints_Top_Users_In_Period_Table( $query, 'admin_screen' );
+ $table->display();
+ }
+
+ /**
+ * Gets the search args submitted by the user.
+ *
+ * @since 1.0.0
+ *
+ * @return array The search args submitted by the user.
+ */
+ protected function get_search_args() {
+
+ $args = array(
+ 'from' => '',
+ 'from_time' => '00:00',
+ 'to' => '',
+ 'to_time' => '23:59',
+ 'points_type' => '-1',
+ );
+
+ if ( ! empty( $_GET['from'] ) ) { // WPCS: CSRF OK.
+ $args['from'] = sanitize_text_field( wp_unslash( $_GET['from'] ) ); // WPCS: CSRF OK.
+ }
+
+ if ( ! empty( $_GET['from_time'] ) ) { // WPCS: CSRF OK.
+ $args['from_time'] = sanitize_text_field( wp_unslash( $_GET['from_time'] ) ); // WPCS: CSRF OK.
+ }
+
+ if ( ! empty( $_GET['to'] ) ) { // WPCS: CSRF OK.
+ $args['to'] = sanitize_text_field( wp_unslash( $_GET['to'] ) ); // WPCS: CSRF OK.
+ }
+
+ if ( ! empty( $_GET['to_time'] ) ) { // WPCS: CSRF OK.
+ $args['to_time'] = sanitize_text_field( wp_unslash( $_GET['to_time'] ) ); // WPCS: CSRF OK.
+ }
+
+ if ( ! empty( $_GET['points_type'] ) && '-1' !== $_GET['points_type'] ) { // WPCS: CSRF OK.
+ $args['points_type'] = sanitize_key( $_GET['points_type'] ); // WPCS: CSRF OK.
+ }
+
+ return $args;
+ }
+}
+
+// EOF
diff --git a/src/classes/block/logs/query.php b/src/classes/block/logs/query.php
new file mode 100644
index 0000000..802aa50
--- /dev/null
+++ b/src/classes/block/logs/query.php
@@ -0,0 +1,145 @@
+ array( 'format' => '%d', 'unsigned' => true ),
+ 'block_id' => array( 'format' => '%d', 'unsigned' => true ),
+ 'user_id' => array( 'format' => '%d', 'unsigned' => true ),
+ 'points' => array( 'format' => '%d', 'unsigned' => true ),
+ // Pseudo-column of the sum of the results.
+ 'total' => array( 'format' => '%d' ),
+ );
+
+ /**
+ * The HAVING clause for the query.
+ *
+ * @since 1.0.0
+ *
+ * @var string
+ */
+ protected $having = '';
+
+ /**
+ * @since 1.0.0
+ *
+ * @param array $args {
+ * The arguments for the query.
+ *
+ * Supports all of the args that WordPoints_DB_Query::__construct() does,
+ * with the following additions/modifications.
+ *
+ * @type string|array $fields Fields to include in the results. Defaults to 'total'
+ * and 'user_id', and *cannot be changed*.
+ * @type string $order_by The field to use to order the results. Default: 'total'.
+ * @type int $id The ID of the block log to retrieve.
+ * @type string $id__compare The comparison operator to use with the above value.
+ * @type int[] $id__in Limit results to these block log IDs.
+ * @type int[] $id__not_in Exclude all block logs with these IDs.
+ * @type int $user_id Limit results to block logs for this user.
+ * @type string $user_id__compare The comparison operator to use with the above value.
+ * @type int[] $user_id__in Limit results to block logs for these users.
+ * @type int[] $user_id__not_in Exclude all block logs for these users from the results.
+ * @type int $block_id Limit results to logs for this block.
+ * @type string $block_id__compare The comparison operator to use with the above value.
+ * @type int[] $block_id__in Limit results to logs for these blocks.
+ * @type int[] $block_id__not_in Exclude all logs for these blocks from the results.
+ * @type int $points Limit results to blocks in which the user earned this number of points. More uses when used with $points__compare.
+ * @type string $points__compare Comparison operator for logs comparison with $points. May be any of these: '=', '<', '>', '<>', '!=', '<=', '>='. Default is '='.
+ * @type int[] $points__in Return only block logs for these points amounts.
+ * @type int[] $points__not_in Exclude block logs for these points amounts from the results.
+ * @type int $total Include only results for this total.
+ * @type string $total__compare The comparison operator to use with the above value.
+ * @type int[] $total__in Limit results to these totals.
+ * @type int[] $total__not_in Exclude users with these totals from the results.
+ * }
+ */
+ public function __construct( array $args = array() ) {
+
+ global $wpdb;
+
+ $this->table_name = $wpdb->base_prefix . 'wordpoints_top_users_in_period_block_logs';
+
+ $this->defaults['order_by'] = 'total';
+
+ parent::__construct( $args );
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_select() {
+
+ // We check this here instead of in the constructor, because of set_args().
+ if ( isset( $this->args['fields'] ) ) {
+ _doing_it_wrong(
+ __METHOD__
+ , "The 'fields' argument is not supported and has no effect."
+ , '1.0.0'
+ );
+ }
+
+ $this->select = 'SELECT SUM(`points`) AS `total`, `user_id`';
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_where() {
+
+ $this->wheres = array();
+
+ // First do just the total column.
+ $this->prepare_column_where( 'total', $this->columns['total'] );
+
+ if ( $this->wheres ) {
+ $this->having = 'HAVING ' . implode( ' AND ', $this->wheres ) . "\n";
+ }
+
+ $this->wheres = array();
+ $this->where = '';
+
+ // Then do everything else.
+ $all_columns = $this->columns;
+
+ unset( $this->columns['total'] );
+
+ parent::prepare_where();
+
+ $this->columns = $all_columns;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_order_by() {
+
+ parent::prepare_order_by();
+
+ $this->order = "GROUP BY `user_id`\n{$this->having}\n{$this->order}";
+ }
+}
+
+// EOF
diff --git a/src/classes/block/type/week/in/seconds.php b/src/classes/block/type/week/in/seconds.php
new file mode 100644
index 0000000..84157f7
--- /dev/null
+++ b/src/classes/block/type/week/in/seconds.php
@@ -0,0 +1,59 @@
+slug = $slug;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function get_slug() {
+ return $this->slug;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function get_block_info( $timestamp ) {
+
+ $start = $timestamp - ( $timestamp % WEEK_IN_SECONDS );
+
+ // We subtract one from the end because the start and end are inclusive.
+ return array( 'start' => $start, 'end' => $start + WEEK_IN_SECONDS - 1 );
+ }
+}
+
+// EOF
diff --git a/src/classes/block/typei.php b/src/classes/block/typei.php
new file mode 100644
index 0000000..0e6ff2c
--- /dev/null
+++ b/src/classes/block/typei.php
@@ -0,0 +1,45 @@
+ array( 'format' => '%d', 'unsigned' => true ),
+ 'block_type' => array( 'format' => '%s' ),
+ 'start_date' => array( 'format' => '%s', 'is_date' => true ),
+ 'end_date' => array( 'format' => '%s', 'is_date' => true ),
+ 'query_signature' => array( 'format' => '%s' ),
+ 'status' => array( 'format' => '%s', 'values' => array( 'draft', 'filled' ) ),
+ );
+
+ /**
+ * @since 1.0.0
+ *
+ * @see WP_Date_Query for the proper arguments for the 'start_date_query' and
+ * 'end_date_query' args.
+ *
+ * @param array $args {
+ * The arguments for the query.
+ *
+ * The arguments are the same as the parent class with the following
+ * additions and modifications.
+ *
+ * @type string $order_by The field to use to order the results. Default: 'start_date'.
+ * @type int $id The ID of the block to retrieve.
+ * @type string $id__compare The comparison operator to use with the above value.
+ * @type int[] $id__in Limit results to these block IDs.
+ * @type int[] $id__not_in Exclude all blocks with these IDs.
+ * @type string $block_type Include only results for this block type.
+ * @type string $block_type__compare The comparison operator to use with the above value.
+ * @type string[] $block_type__in Limit results to these block types.
+ * @type string[] $block_type__not_in Exclude blocks of these types from the results.
+ * @type string $query_signature Include only results for this query signature.
+ * @type string $query_signature__compare The comparison operator to use with the above value.
+ * @type string[] $query_signature__in Limit results to these query signatures.
+ * @type string[] $query_signature__not_in Exclude blocks for these query signatures from the results.
+ * @type string $status Include only results for blocks with this status.
+ * @type string $status__compare The comparison operator to use with the above value.
+ * @type string[] $status__in Limit results to blocks with these these statuses.
+ * @type string[] $status__not_in Exclude blocks with these statuses from the results.
+ * @type array $start_date_query Arguments for a WP_Date_Query with the 'start_date' as the default column.
+ * @type array $end_date_query Arguments for a WP_Date_Query with the 'end_date' as the default column.
+ * }
+ */
+ public function __construct( $args = array() ) {
+
+ global $wpdb;
+
+ $this->table_name = $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks';
+
+ $this->defaults['order_by'] = 'start_date';
+
+ parent::__construct( $args );
+ }
+}
+
+// EOF
diff --git a/src/classes/index.php b/src/classes/index.php
new file mode 100644
index 0000000..d81081a
--- /dev/null
+++ b/src/classes/index.php
@@ -0,0 +1,29 @@
+ 'query/cachei.php',
+ 'wordpoints_top_users_in_period_block_typei' => 'block/typei.php',
+ 'wordpoints_top_users_in_period_admin_screen' => 'admin/screen.php',
+ 'wordpoints_top_users_in_period_block_logs_query' => 'block/logs/query.php',
+ 'wordpoints_top_users_in_period_block_type_week_in_seconds' => 'block/type/week/in/seconds.php',
+ 'wordpoints_top_users_in_period_blocks_query' => 'blocks/query.php',
+ 'wordpoints_top_users_in_period_points_logs_query' => 'points/logs/query.php',
+ 'wordpoints_top_users_in_period_query' => 'query.php',
+ 'wordpoints_top_users_in_period_query_cache_flusher' => 'query/cache/flusher.php',
+ 'wordpoints_top_users_in_period_query_cache_index' => 'query/cache/index.php',
+ 'wordpoints_top_users_in_period_query_cache_transients' => 'query/cache/transients.php',
+ 'wordpoints_top_users_in_period_shortcode_dynamic' => 'shortcode/dynamic.php',
+ 'wordpoints_top_users_in_period_shortcode_fixed' => 'shortcode/fixed.php',
+ 'wordpoints_top_users_in_period_table' => 'table.php',
+ 'wordpoints_top_users_in_period_widget_dynamic' => 'widget/dynamic.php',
+ 'wordpoints_top_users_in_period_widget_fixed' => 'widget/fixed.php',
+ // }
+);
diff --git a/src/classes/points/logs/query.php b/src/classes/points/logs/query.php
new file mode 100644
index 0000000..e20c80a
--- /dev/null
+++ b/src/classes/points/logs/query.php
@@ -0,0 +1,113 @@
+defaults because the value would be overwritten by the
+ // parent constructor.
+ if ( ! isset( $args['order_by'] ) ) {
+ $args['order_by'] = 'total';
+ }
+
+ $this->columns['total'] = array( 'format' => '%d' );
+
+ parent::__construct( $args );
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_select() {
+
+ // We check this here instead of in the constructor, because of set_args().
+ if ( isset( $this->args['fields'] ) ) {
+ _doing_it_wrong(
+ __METHOD__
+ , "The 'fields' argument is not supported and has no effect."
+ , '1.0.0'
+ );
+ }
+
+ $this->select = 'SELECT SUM(`points`) AS `total`, `user_id`';
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_where() {
+
+ $this->wheres = array();
+
+ // First do just the total column.
+ $this->prepare_column_where( 'total', $this->columns['total'] );
+
+ if ( $this->wheres ) {
+ $this->having = 'HAVING ' . implode( ' AND ', $this->wheres ) . "\n";
+ }
+
+ $this->wheres = array();
+ $this->where = '';
+
+ // Then do everything else.
+ $all_columns = $this->columns;
+
+ unset( $this->columns['total'] );
+
+ parent::prepare_where();
+
+ $this->columns = $all_columns;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_order_by() {
+
+ parent::prepare_order_by();
+
+ $this->order = "GROUP BY `user_id`\n{$this->having}\n{$this->order}";
+ }
+}
+
+// EOF
diff --git a/src/classes/query.php b/src/classes/query.php
new file mode 100644
index 0000000..70699a0
--- /dev/null
+++ b/src/classes/query.php
@@ -0,0 +1,1159 @@
+open_ended = true;
+
+ $end_date = new DateTime(
+ '@' . current_time( 'timestamp', true )
+ , new DateTimeZone( 'UTC' )
+ );
+ }
+
+ $this->start_date = $start_date;
+ $this->end_date = $end_date;
+
+ $this->start_timestamp = (int) $this->start_date->format( 'U' );
+ $this->end_timestamp = (int) $this->end_date->format( 'U' );
+
+ // If the dates are invalid we return an error message below.
+ if ( $this->end_timestamp < $this->start_timestamp ) {
+ return;
+ }
+
+ parent::__construct( $args );
+
+ $this->table_name = null;
+ }
+
+ /**
+ * Returns the start date for the query.
+ *
+ * The returned object is a clone, so modifications have no effect. It is
+ * essentially read-only.
+ *
+ * @since 1.0.0
+ *
+ * @return DateTime The start date object.
+ */
+ public function get_start_date() {
+ return clone $this->start_date;
+ }
+
+ /**
+ * Returns the end date for the query.
+ *
+ * The returned object is a clone, so modifications have no effect. It is
+ * essentially read-only.
+ *
+ * @since 1.0.0
+ *
+ * @return DateTime The end date object.
+ */
+ public function get_end_date() {
+ return clone $this->end_date;
+ }
+
+ /**
+ * Returns the args for the query.
+ *
+ * @since 1.0.0
+ *
+ * @return array The query args.
+ */
+ public function get_args() {
+ return $this->args;
+ }
+
+ /**
+ * Checks if this is a network query.
+ *
+ * A query is considered a network-scope query if it affects other sites on the
+ * network beside the current one.
+ *
+ * @since 1.0.0
+ *
+ * @return bool Whether this is a network query.
+ */
+ public function is_network_scope() {
+
+ if ( ! is_multisite() ) {
+ return false;
+ }
+
+ return (
+ empty( $this->args['blog_id'] )
+ || (
+ isset( $this->args['blog_id__compare'] )
+ && '=' !== $this->args['blog_id__compare']
+ )
+ || get_current_blog_id() !== $this->args['blog_id']
+ );
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function count() {
+
+ _doing_it_wrong(
+ __METHOD__
+ , 'The top users query does not support counting.'
+ , '1.0.0'
+ );
+
+ return false;
+ }
+
+ /**
+ * Gets the query results.
+ *
+ * @since 1.0.0
+ *
+ * @return object[]|WP_Error The results, or an error object on failure.
+ */
+ public function get( $unused = null ) {
+
+ if ( isset( $unused ) ) {
+ _doing_it_wrong( __METHOD__, 'The top users query doesn\'t use the $method parameter.', '1.0.0' );
+ }
+
+ if ( $this->end_timestamp < $this->start_timestamp ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_query_end_before_start'
+ , __( 'End date cannot come before start date.', 'wordpoints-top-users-in-period' )
+ );
+ }
+
+ $this->args = $this->clean_query_args( $this->args );
+
+ $cache = $this->get_cache();
+ $cached_results = $cache->get();
+
+ if ( false !== $cached_results ) {
+ return $cached_results;
+ }
+
+ $sql = $this->get_sql();
+
+ if ( is_wp_error( $sql ) ) {
+ return $sql;
+ }
+
+ global $wpdb;
+
+ $results = $wpdb->get_results( $sql ); // WPCS: unprepared SQL, cache OK.
+
+ $cache->set( $results );
+
+ return $results;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function get_sql( $unused = null ) {
+
+ if ( isset( $unused ) ) {
+ _doing_it_wrong( __METHOD__, 'The top users query doesn\'t use the $select_type parameter.', '1.0.0' );
+ }
+
+ if ( ! $this->is_query_ready ) {
+ $this->blocks_query_signature = $this->generate_blocks_query_signature();
+ }
+
+ $this->block_type = $this->get_block_type();
+ $this->start_block_info = $this->block_type->get_block_info( $this->start_timestamp );
+ $this->end_block_info = $this->block_type->get_block_info( $this->end_timestamp );
+
+ if ( ! $this->should_use_block_logs() ) {
+
+ return $this->get_sql_for_points_logs();
+
+ } elseif (
+ $this->start_timestamp === $this->start_block_info['start']
+ && $this->end_timestamp === $this->end_block_info['end']
+ ) {
+
+ return $this->get_sql_for_block_logs();
+
+ } else {
+
+ return $this->get_sql_for_both();
+ }
+ }
+
+ //
+ // Protected Methods.
+ //
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_query() {
+
+ if ( ! $this->is_query_ready ) {
+ $this->prepare_having();
+ }
+
+ parent::prepare_query();
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_select() {}
+
+ /**
+ * @since 1.0.0
+ */
+ protected function prepare_where() {}
+
+ /**
+ * Prepares the SQL for the `HAVING` clause for the query.
+ *
+ * @since 1.0.0
+ */
+ protected function prepare_having() {
+
+ $this->having = '';
+
+ $this->prepare_column_where( 'total', array( 'format' => '%d' ) );
+
+ if ( $this->wheres ) {
+ $this->having = 'HAVING ' . implode( ' AND ', $this->wheres ) . "\n";
+ }
+ }
+
+ /**
+ * Gets the block type to be used by this query.
+ *
+ * @since 1.0.0
+ *
+ * @return WordPoints_Top_Users_In_Period_Block_TypeI The block type.
+ */
+ protected function get_block_type() {
+
+ $slug = 'week_in_seconds';
+
+ /**
+ * Filter the block type to use for a query.
+ *
+ * @since 1.0.0
+ *
+ * @param string $slug The slug of the block type to use.
+ * @param object $query The query.
+ */
+ $slug = apply_filters(
+ 'wordpoints_top_user_in_period_query_block_type'
+ , $slug
+ , $this
+ );
+
+ $block_type = wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'block_types' )
+ ->get( $slug );
+
+ if ( ! $block_type instanceof WordPoints_Top_Users_In_Period_Block_TypeI ) {
+ return new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'week_in_seconds'
+ );
+ }
+
+ return $block_type;
+ }
+
+ /**
+ * Gets the cache object that should be used by this query.
+ *
+ * @since 1.0.0
+ *
+ * @return WordPoints_Top_Users_In_Period_Query_CacheI The cache object.
+ */
+ protected function get_cache() {
+
+ $slug = 'transients';
+
+ /**
+ * Filter the cache object to use for a query.
+ *
+ * @since 1.0.0
+ *
+ * @param string $slug The slug of the cache object to use.
+ * @param object $query The query.
+ */
+ $slug = apply_filters(
+ 'wordpoints_top_user_in_period_query_cache'
+ , $slug
+ , $this
+ );
+
+ $args = $this->args;
+ $args['start_timestamp'] = $this->start_timestamp;
+
+ if ( ! $this->open_ended ) {
+ $args['end_timestamp'] = $this->end_timestamp;
+ }
+
+ $is_network_query = $this->is_network_scope();
+
+ $cache = wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->get( $slug, array( $args, $is_network_query ) );
+
+ if ( ! $cache instanceof WordPoints_Top_Users_In_Period_Query_CacheI ) {
+
+ $slug = 'transients';
+
+ $cache = new WordPoints_Top_Users_In_Period_Query_Cache_Transients(
+ $slug
+ , $args
+ , $is_network_query
+ );
+ }
+
+ if ( $this->open_ended ) {
+ $cache_index = new WordPoints_Top_Users_In_Period_Query_Cache_Index(
+ $is_network_query
+ );
+
+ $cache_index->add( $slug, $this->args, $args['start_timestamp'] );
+ }
+
+ return $cache;
+ }
+
+ /**
+ * Cleans query args to standardize them for creating a query signature.
+ *
+ * Query signatures are utilized both for traditional caching and for the blocks.
+ *
+ * @since 1.0.0
+ *
+ * @param array $args The query args to clean.
+ *
+ * @return array The cleaned args.
+ */
+ protected function clean_query_args( array $args ) {
+
+ $not_cols = array(
+ 'start' => true,
+ 'limit' => true,
+ 'order' => true,
+ 'order_by' => true,
+ 'fields' => true,
+ 'date_query' => true,
+ 'meta_query' => true,
+ 'meta_key' => true,
+ 'meta_value' => true,
+ 'meta_compare' => true,
+ );
+
+ foreach ( $args as $arg => $value ) {
+
+ if ( isset( $not_cols[ $arg ] ) ) {
+ continue;
+ }
+
+ $column = $arg;
+ $operator = null;
+
+ if ( strpos( $column, '__' ) ) {
+ list( $column, $operator ) = explode( '__', $arg );
+ }
+
+ if ( ! isset( $this->columns[ $column ] ) ) {
+ unset( $args[ $arg ] );
+ continue;
+ }
+
+ // The compare operator is ignored if the value isn't set.
+ if (
+ 'compare' === $operator
+ && 'NOT EXISTS' !== $value
+ && ! isset( $args[ $column ] )
+ ) {
+ unset( $args[ $arg ] );
+ }
+
+ if ( 'in' === $operator || 'not_in' === $operator ) {
+
+ // In and not in are ignored when a direct value is set.
+ if ( isset( $args[ $column ] ) ) {
+ unset( $args[ $arg ] );
+ continue;
+ }
+
+ if ( '%d' === $this->columns[ $column ]['format'] ) {
+ $value = array_map( 'wordpoints_int', $value );
+ }
+
+ $value = array_unique( $value );
+
+ if ( count( $value ) === 1 ) {
+
+ unset( $args[ $arg ] );
+
+ $args[ $column ] = reset( $value );
+
+ if ( 'not_in' === $operator ) {
+ $args[ "{$column}__compare" ] = '!=';
+ }
+
+ } else {
+
+ sort( $value );
+
+ $args[ $arg ] = $value;
+ }
+
+ } elseif ( ! isset( $operator ) ) {
+
+ if ( '%d' === $this->columns[ $column ]['format'] ) {
+ $args[ $arg ] = wordpoints_int( $value );
+ }
+
+ } // End if ( in or not in ) elseif ( just column ).
+
+ } // End foreach ( args ).
+
+ return $args;
+ }
+
+ /**
+ * Generates the query signature to use for the blocks table.
+ *
+ * @since 1.0.0
+ *
+ * @return string The query signature.
+ */
+ protected function generate_blocks_query_signature() {
+
+ $args = $this->get_block_signature_args();
+
+ ksort( $args );
+
+ return wordpoints_hash( wp_json_encode( $args ) );
+ }
+
+ /**
+ * Gets just the args that are included in the block signature.
+ *
+ * @since 1.0.0
+ *
+ * @return array The args that are included in the block signature.
+ */
+ protected function get_block_signature_args() {
+
+ $args = $this->args;
+
+ // The user and total args aren't needed in the signature since the blocks
+ // table includes a `user_id` column, and the total is calculated across
+ // blocks.
+ unset(
+ $args['limit'],
+ $args['start'],
+ $args['order'],
+ $args['order_by'],
+ $args['fields'],
+ $args['user_id'],
+ $args['user_id__in'],
+ $args['user_id__not_in'],
+ $args['user_id__compare'],
+ $args['total'],
+ $args['total__in'],
+ $args['total__not_in'],
+ $args['total__compare']
+ );
+
+ return $args;
+ }
+
+ /**
+ * Checks whether the block logs should be used, or just the regular logs only.
+ *
+ * @since 1.0.0
+ *
+ * @return bool Whether to use the block logs or not.
+ */
+ protected function should_use_block_logs() {
+
+ $use_blocks = false;
+
+ $one_block_only = $this->start_block_info === $this->end_block_info;
+
+ // Only use the blocks if an entire block is going to fall within the period.
+ if (
+ // That only happens when more than two blocks are involved.
+ (
+ $this->start_block_info['end'] + 1 !== $this->end_block_info['start']
+ && ! $one_block_only
+ )
+
+ // Or, when there are only two blocks involved, but one or both fit the
+ // period exactly.
+ || (
+ $this->start_block_info['end'] + 1 === $this->end_block_info['start']
+ && (
+ $this->start_block_info['start'] === $this->start_timestamp
+ || $this->end_block_info['end'] === $this->end_timestamp
+ )
+ )
+
+ // Or, when there is only one block involved, but it fits exactly.
+ || (
+ $one_block_only
+ && $this->start_block_info['start'] === $this->start_timestamp
+ && $this->end_block_info['end'] === $this->end_timestamp
+ )
+ ) {
+ $use_blocks = true;
+ }
+
+ /**
+ * Filters whether to use the block logs for a query.
+ *
+ * If the block logs aren't used, just the regular points logs will be
+ * queried to get the result.
+ *
+ * @since 1.0.0
+ *
+ * @param bool $use_blocks Whether to use the block logs.
+ * @param object $query The query.
+ */
+ $use_blocks = apply_filters(
+ 'wordpoints_top_users_in_period_query_use_blocks'
+ , $use_blocks
+ , $this
+ );
+
+ return $use_blocks;
+ }
+
+ /**
+ * Gets the SQL for a query using only the regular points logs table.
+ *
+ * @since 1.0.0
+ *
+ * @return string The SQL.
+ */
+ protected function get_sql_for_points_logs() {
+
+ $args = $this->args;
+
+ $args['date_query'][] = array(
+ 'inclusive' => true,
+ 'after' => '@' . $this->start_timestamp,
+ 'before' => '@' . $this->end_timestamp,
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Points_Logs_Query( $args );
+
+ return $query->get_sql();
+ }
+
+ /**
+ * Gets the SQL for the query using only the block logs table.
+ *
+ * @since 1.0.0
+ *
+ * @return string|WP_Error The SQL, or an error on failure.
+ */
+ protected function get_sql_for_block_logs() {
+
+ $blocks = $this->get_and_verify_blocks();
+
+ if ( is_wp_error( $blocks ) ) {
+ return $blocks;
+ }
+
+ return $this->get_block_logs_query( $blocks )->get_sql();
+ }
+
+ /**
+ * Gets the SQL using a hybrid query involving both the regular and block tables.
+ *
+ * @since 1.0.0
+ *
+ * @return string|WP_Error The SQL, or an error object on failure.
+ */
+ protected function get_sql_for_both() {
+
+ $blocks = $this->get_and_verify_blocks();
+
+ if ( is_wp_error( $blocks ) ) {
+ return $blocks;
+ }
+
+ $this->prepare_query();
+
+ $blocks_query = $this->get_block_logs_query( $blocks, false );
+ $logs_query = $this->get_points_logs_query( $blocks );
+
+ return "
+ SELECT SUM(`total`) AS `total`, `user_id`
+ FROM (
+ {$blocks_query->get_sql()}
+ UNION ALL
+ {$logs_query->get_sql()}
+ ) AS `t`
+ {$this->order}
+ {$this->limit}
+ ";
+ }
+
+ /**
+ * Gets the query for the block logs table.
+ *
+ * @since 1.0.0
+ *
+ * @param object[] $blocks The blocks to query.
+ * @param bool $inherit_limit_order Whether to use the limit and order
+ * related args from the main query or not.
+ *
+ * @return WordPoints_Top_Users_In_Period_Block_Logs_Query The block logs query.
+ */
+ protected function get_block_logs_query( $blocks, $inherit_limit_order = true ) {
+
+ $args = array( 'block_id__in' => wp_list_pluck( $blocks, 'id' ) );
+
+ $inherited_args = array(
+ 'user_id',
+ 'user_id__in',
+ 'user_id__not_in',
+ 'user_id__compare',
+ );
+
+ if ( $inherit_limit_order ) {
+ $inherited_args[] = 'limit';
+ $inherited_args[] = 'start';
+ $inherited_args[] = 'order';
+ $inherited_args[] = 'order_by';
+ $inherited_args[] = 'total';
+ $inherited_args[] = 'total__in';
+ $inherited_args[] = 'total__not_in';
+ $inherited_args[] = 'total__compare';
+ } else {
+ $args['order_by'] = false;
+ }
+
+ foreach ( $inherited_args as $arg ) {
+ if ( isset( $this->args[ $arg ] ) ) {
+ $args[ $arg ] = $this->args[ $arg ];
+ }
+ }
+
+ return new WordPoints_Top_Users_In_Period_Block_Logs_Query( $args );
+ }
+
+ /**
+ * Gets the points logs query for use along with a block logs query.
+ *
+ * @since 1.0.0
+ *
+ * @param object[] $blocks The blocks being queried for the period.
+ *
+ * @return WordPoints_Top_Users_In_Period_Points_Logs_Query The points logs query.
+ */
+ protected function get_points_logs_query( $blocks ) {
+
+ $args = $this->args;
+
+ // The conditions for the total need to be set only for the combined query.
+ unset(
+ $args['limit'],
+ $args['start'],
+ $args['total'],
+ $args['total__in'],
+ $args['total__not_in'],
+ $args['total__compare']
+ );
+
+ $args['order_by'] = false;
+
+ $first_block = $blocks[0];
+ $last_block = end( $blocks );
+
+ $first_block_start = strtotime( $first_block->start_date . '+0000' );
+ $last_block_end = strtotime( $last_block->end_date . '+0000' );
+
+ $date_query = array( 'relation' => 'OR' );
+
+ if ( $this->start_timestamp !== $first_block_start ) {
+ $date_query[] = array(
+ 'inclusive' => true,
+ 'after' => '@' . $this->start_timestamp,
+ 'before' => '@' . ( $first_block_start - 1 ), // Because inclusive.
+ );
+ }
+
+ if ( $this->end_timestamp !== $last_block_end ) {
+ $date_query[] = array(
+ 'inclusive' => true,
+ 'after' => '@' . ( $last_block_end + 1 ), // Because inclusive.
+ 'before' => '@' . $this->end_timestamp,
+ );
+ }
+
+ $args['date_query'][] = $date_query;
+
+ return new WordPoints_Top_Users_In_Period_Points_Logs_Query( $args );
+ }
+
+ /**
+ * Gets the blocks involved in this query and verifies that they can be used.
+ *
+ * @since 1.0.0
+ *
+ * @return object[]|WP_Error The blocks, or an error object on failure.
+ */
+ protected function get_and_verify_blocks() {
+
+ // See if any blocks exist that fall into this period.
+ $blocks = $this->get_blocks();
+
+ // If any of these blocks are currently in the process of being filled by
+ // another query, we can't proceed.
+ $draft_blocks = $this->check_for_draft_blocks( $blocks );
+
+ if ( $draft_blocks ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_query_draft_blocks'
+ , ''
+ , $draft_blocks
+ );
+ }
+
+ // If any of the blocks are not filled yet, we need to fill them.
+ $missing_blocks = $this->check_for_missing_blocks( $blocks );
+
+ if ( ! empty( $missing_blocks ) ) {
+
+ foreach ( $missing_blocks as $block ) {
+ if ( ! $this->fill_block_logs( $block ) ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_query_failed_filling_block'
+ , ''
+ , $block
+ );
+ }
+ }
+
+ return $this->get_and_verify_blocks();
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Gets the list of blocks that fall within this query's period.
+ *
+ * @since 1.0.0
+ *
+ * @return object[] The blocks.
+ */
+ protected function get_blocks() {
+
+ $block_query = new WordPoints_Top_Users_In_Period_Blocks_Query(
+ array(
+ 'block_type' => $this->block_type->get_slug(),
+ 'start_date_query' => array(
+ 'inclusive' => true,
+ 'after' => '@' . $this->start_timestamp,
+ ),
+ 'end_date_query' => array(
+ 'inclusive' => true,
+ 'before' => '@' . $this->end_timestamp,
+ ),
+ 'query_signature' => $this->blocks_query_signature,
+ 'order_by' => 'start_date',
+ 'order' => 'ASC',
+ )
+ );
+
+ return $block_query->get();
+ }
+
+ /**
+ * Checks for any blocks that are still drafts.
+ *
+ * @since 1.0.0
+ *
+ * @param object[] $blocks The blocks.
+ *
+ * @return object[] The draft blocks, if any.
+ */
+ protected function check_for_draft_blocks( $blocks ) {
+
+ return wp_list_filter( $blocks, array( 'status' => 'draft' ) );
+ }
+
+ /**
+ * Checks for any missing blocks for the period covered by the query.
+ *
+ * @since 1.0.0
+ *
+ * @param object[] $blocks The blocks that are already in the database.
+ *
+ * @return array[] Start and end dates for any missing blocks.
+ */
+ protected function check_for_missing_blocks( $blocks ) {
+
+ $missing_blocks = array();
+
+ $start = $this->start_block_info['start'];
+
+ if ( $start !== $this->start_timestamp ) {
+ $start = $this->start_block_info['end'] + 1;
+ }
+
+ $end_block = $this->end_block_info;
+
+ if ( $end_block['end'] !== $this->end_timestamp ) {
+ $end_block = $this->block_type->get_block_info( $end_block['start'] - 1 );
+ }
+
+ if ( empty( $blocks ) ) {
+ return $this->note_missing_blocks( $end_block['end'] + 1, $start );
+ }
+
+ $first_block = array_shift( $blocks );
+ $first_block_start = strtotime( $first_block->start_date . '+0000' );
+
+ // Check if there are any blocks missing at the start.
+ if ( $first_block_start !== $start ) {
+
+ $missing_blocks = array_merge(
+ $missing_blocks
+ , $this->note_missing_blocks( $first_block_start, $start )
+ );
+ }
+
+ $previous_block = $first_block;
+
+ // Check if there are any holes between blocks.
+ foreach ( $blocks as $index => $block ) {
+
+ $missing_blocks = array_merge(
+ $missing_blocks
+ , $this->note_missing_blocks(
+ strtotime( $block->start_date . '+0000' )
+ , strtotime( $previous_block->end_date . '+0000' ) + 1
+ )
+ );
+
+ $previous_block = $block;
+ }
+
+ $last_block = $previous_block;
+ $last_block_start = strtotime( $last_block->start_date . '+0000' );
+
+ // Check if there are any blocks missing at the end.
+ if ( $last_block_start !== $end_block['start'] ) {
+
+ $missing_blocks = array_merge(
+ $missing_blocks
+ , $this->note_missing_blocks(
+ $end_block['end'] + 1
+ , strtotime( $last_block->end_date . '+0000' ) + 1
+ )
+ );
+ }
+
+ return $missing_blocks;
+ }
+
+ /**
+ * Makes a note of all blocks that are missing between two timestamps.
+ *
+ * Each missing block is then saved to the database as a "draft", to have the
+ * data for it in the block logs table filled momentarily.
+ *
+ * @since 1.0.0
+ *
+ * @param int $actual_start The timestamp for when the next detected block
+ * actually starts.
+ * @param int $expected_start The timestamp for when the next block was expected
+ * to start.
+ *
+ * @return array[] Start and end dates for any missing blocks.
+ */
+ protected function note_missing_blocks( $actual_start, $expected_start ) {
+
+ $missing_blocks = array();
+
+ // If we start when expected, we're OK.
+ if ( $actual_start <= $expected_start ) {
+ return $missing_blocks;
+ }
+
+ $block_end = $expected_start - 1;
+
+ // Otherwise note the blocks that would be expected between the expected
+ // starting point and the actual starting point.
+ while ( $block_end + 1 < $actual_start ) {
+
+ $block_info = $this->block_type->get_block_info( $block_end + 1 );
+
+ $block_end = $block_info['end'];
+
+ // Save the draft block here to reduce chance of race conditions where
+ // another query is trying to fill the same block.
+ $block_info['id'] = $this->save_draft_block( $block_info );
+
+ $missing_blocks[] = $block_info;
+ }
+
+ return $missing_blocks;
+ }
+
+ /**
+ * Saves a block as a draft.
+ *
+ * @since 1.0.0
+ *
+ * @param array $block The block data.
+ *
+ * @return int|false The block ID, or false on failure.
+ */
+ protected function save_draft_block( $block ) {
+
+ global $wpdb;
+
+ $rows = $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'start_date' => date( 'Y-m-d H:i:s', $block['start'] ),
+ 'end_date' => date( 'Y-m-d H:i:s', $block['end'] ),
+ 'block_type' => $this->block_type->get_slug(),
+ 'query_signature' => $this->blocks_query_signature,
+ 'status' => 'draft',
+ )
+ );
+
+ $id = (int) $wpdb->insert_id;
+
+ if ( 1 !== (int) $rows || $id < 1 ) {
+ return false;
+ }
+
+ return $id;
+ }
+
+ /**
+ * Fills in all of the data in the block logs table for a block.
+ *
+ * @since 1.0.0
+ *
+ * @param array $block The block to fill in the logs the data for.
+ *
+ * @return bool Whether the block was filled successfully.
+ */
+ protected function fill_block_logs( $block ) {
+
+ global $wpdb;
+
+ if ( ! $block['id'] ) {
+ return false;
+ }
+
+ $this->no_interruptions();
+
+ $args = $this->get_block_signature_args();
+
+ // Ordering isn't necessary here, so explicitly set it to false.
+ $args['order_by'] = false;
+
+ $args['date_query'][] = array(
+ 'inclusive' => true,
+ 'after' => '@' . $block['start'],
+ 'before' => '@' . $block['end'],
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Points_Logs_Query( $args );
+
+ $wpdb->query( // WPCS: unprepared SQL OK.
+ "
+ INSERT INTO `{$wpdb->base_prefix}wordpoints_top_users_in_period_block_logs`
+ ( `block_id`, `points`, `user_id` )
+ SELECT " . (int) $block['id'] . ',' . substr( trim( $query->get_sql() ), 6 )
+ ); // WPCS: cache OK.
+
+ $this->publish_block( $block['id'] );
+
+ return true;
+ }
+
+ /**
+ * Prevents any interruptions from occurring during the query.
+ *
+ * @since 1.0.0
+ */
+ protected function no_interruptions() {
+
+ static $done = false;
+
+ if ( $done ) {
+ return;
+ }
+
+ ignore_user_abort( true );
+
+ if ( ! wordpoints_is_function_disabled( 'set_time_limit' ) ) {
+ set_time_limit( 0 );
+ }
+
+ $done = true;
+ }
+
+ /**
+ * Publishes a block.
+ *
+ * Blocks are initially inserted into the database with the "draft" status, until
+ * they are filled. Then we can "publish" them by giving them a status of
+ * "filled".
+ *
+ * @since 1.0.0
+ *
+ * @param int $block_id The ID of the block to mark as published.
+ *
+ * @return bool Whether the block was published successfully.
+ */
+ protected function publish_block( $block_id ) {
+
+ global $wpdb;
+
+ $rows = $wpdb->update(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array( 'status' => 'filled' )
+ , array( 'id' => $block_id )
+ ); // WPCS: cache OK.
+
+ if ( 1 !== (int) $rows ) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+// EOF
diff --git a/src/classes/query/cache/flusher.php b/src/classes/query/cache/flusher.php
new file mode 100644
index 0000000..ee7dfc1
--- /dev/null
+++ b/src/classes/query/cache/flusher.php
@@ -0,0 +1,185 @@
+args = $args;
+ }
+
+ /**
+ * Flushes the query caches.
+ *
+ * @since 1.0.0
+ */
+ public function flush() {
+
+ $this->query_caches = wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' );
+
+ $this->flush_caches();
+
+ if ( is_multisite() ) {
+ $this->flush_caches( true );
+ }
+ }
+
+ /**
+ * Flushes a given set of queries' caches.
+ *
+ * @since 1.0.0
+ *
+ * @param bool $network_wide Whether to flush the network-wide caches or not.
+ */
+ protected function flush_caches( $network_wide = false ) {
+
+ $caches = new WordPoints_Top_Users_In_Period_Query_Cache_Index( $network_wide );
+
+ foreach ( $caches->get() as $query ) {
+
+ if ( ! $this->should_flush_query( $query['args'] ) ) {
+ continue;
+ }
+
+ foreach ( $query['caches'] as $type => $dates ) {
+
+ if ( ! $this->query_caches->is_registered( $type ) ) {
+ continue;
+ }
+
+ foreach ( $dates as $date => $unused ) {
+
+ $args = $query['args'];
+ $args['start_timestamp'] = $date;
+
+ /** @var WordPoints_Top_Users_In_Period_Query_CacheI $cache */
+ $cache = $this->query_caches->get( $type, array( $args, $network_wide ) );
+ $cache->delete();
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if a given query's cache should be flushed.
+ *
+ * @since 1.0.0
+ *
+ * @param array $args The query args to check based on.
+ *
+ * @return bool Whether this query's cache should be flushed.
+ */
+ protected function should_flush_query( $args ) {
+
+ $arg_slugs = array(
+ 'points_type',
+ 'log_type',
+ 'user_id',
+ 'blog_id',
+ 'site_id',
+ );
+
+ foreach ( $arg_slugs as $arg ) {
+
+ if ( isset( $this->args[ $arg ] ) ) {
+ if ( ! $this->query_arg_matches_value( $args, $arg ) ) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a query arg matches a value that we are flushing relative to.
+ *
+ * @since 1.0.0
+ *
+ * @param array $args The query args.
+ * @param string $arg The slug of the arg to check.
+ *
+ * @return bool Whether the arg matches the value this flusher was passed.
+ */
+ protected function query_arg_matches_value( $args, $arg ) {
+
+ if ( isset( $args[ $arg ] ) ) {
+
+ if (
+ ! isset( $args[ "{$arg}__compare" ] )
+ || '=' === $args[ "{$arg}__compare" ]
+ ) {
+
+ if ( $args[ $arg ] === $this->args[ $arg ] ) {
+ return true;
+ }
+
+ } else {
+ return true;
+ }
+
+ } elseif ( isset( $args[ "{$arg}__in" ] ) ) {
+
+ if ( in_array( $this->args[ $arg ], $args[ "{$arg}__in" ], true ) ) {
+ return true;
+ }
+
+ } elseif ( isset( $args[ "{$arg}__not_in" ] ) ) {
+
+ if ( ! in_array( $this->args[ $arg ], $args[ "{$arg}__not_in" ], true ) ) {
+ return true;
+ }
+
+ } else {
+
+ // If the arg isn't referenced in the query at all, then there are no
+ // restrictions, so it matches.
+ return true;
+ }
+
+ return false;
+ }
+}
+
+// EOF
diff --git a/src/classes/query/cache/index.php b/src/classes/query/cache/index.php
new file mode 100644
index 0000000..ff50f1c
--- /dev/null
+++ b/src/classes/query/cache/index.php
@@ -0,0 +1,95 @@
+network_wide = $network_wide;
+ }
+
+ /**
+ * Gets all of the query caches in the index.
+ *
+ * @since 1.0.0
+ *
+ * @return array The query caches.
+ */
+ public function get() {
+
+ return wordpoints_get_maybe_network_array_option(
+ 'wordpoints_top_users_in_period_query_cache_index'
+ , $this->network_wide
+ );
+ }
+
+ /**
+ * Adds a query to the index.
+ *
+ * @since 1.0.0
+ *
+ * @param string $type The type of cache used for the query.
+ * @param array $query_args The args for the query.
+ * @param int $start_timestamp The start time of the query.
+ */
+ public function add( $type, $query_args, $start_timestamp ) {
+
+ ksort( $query_args );
+
+ $query_signature = wordpoints_hash( wp_json_encode( $query_args ) );
+
+ $queries = $this->get();
+ $queries[ $query_signature ]['args'] = $query_args;
+ $queries[ $query_signature ]['caches'][ $type ][ $start_timestamp ] = true;
+
+ $this->save( $queries );
+ }
+
+ /**
+ * Saves the query index.
+ *
+ * @since 1.0.0
+ *
+ * @param array $queries The cached queries.
+ */
+ protected function save( array $queries ) {
+
+ wordpoints_update_maybe_network_option(
+ 'wordpoints_top_users_in_period_query_cache_index'
+ , $queries
+ , $this->network_wide
+ );
+ }
+
+}
+
+// EOF
diff --git a/src/classes/query/cache/transients.php b/src/classes/query/cache/transients.php
new file mode 100644
index 0000000..73df379
--- /dev/null
+++ b/src/classes/query/cache/transients.php
@@ -0,0 +1,91 @@
+transient_name = "wordpoints_top_users_in_period_query_{$query_signature}";
+
+ $this->is_network_query = $network_wide;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function get() {
+
+ if ( $this->is_network_query ) {
+ return get_site_transient( $this->transient_name );
+ } else {
+ return get_transient( $this->transient_name );
+ }
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function set( $value ) {
+
+ if ( $this->is_network_query ) {
+ return set_site_transient( $this->transient_name, $value );
+ } else {
+ return set_transient( $this->transient_name, $value );
+ }
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function delete() {
+
+ if ( $this->is_network_query ) {
+ return delete_site_transient( $this->transient_name );
+ } else {
+ return delete_transient( $this->transient_name );
+ }
+ }
+}
+
+// EOF
diff --git a/src/classes/query/cachei.php b/src/classes/query/cachei.php
new file mode 100644
index 0000000..f17ced9
--- /dev/null
+++ b/src/classes/query/cachei.php
@@ -0,0 +1,47 @@
+ 10,
+ 'points_type' => '',
+ 'length' => 1,
+ 'units' => 'days',
+ 'relative_to' => 'present',
+ );
+
+ /**
+ * @since 1.0.0
+ */
+ protected function verify_atts() {
+
+ if ( ! wordpoints_posint( $this->atts['length'] ) ) {
+ $this->atts['length'] = $this->pairs['length'];
+ }
+
+ $options = array( 'present' => true, 'calendar' => true );
+
+ if ( ! isset( $this->atts['relative_to'], $options[ $this->atts['relative_to'] ] ) ) {
+ $this->atts['relative_to'] = $this->pairs['relative_to'];
+ }
+
+ $options = array(
+ 'seconds' => true,
+ 'minutes' => true,
+ 'hours' => true,
+ 'days' => true,
+ 'weeks' => true,
+ 'months' => true,
+ );
+
+ if ( ! isset( $this->atts['units'], $options[ $this->atts['units'] ] ) ) {
+ $this->atts['units'] = $this->pairs['units'];
+ }
+
+ return parent::verify_atts();
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function generate() {
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ if ( 'present' === $this->atts['relative_to'] ) {
+
+ $from = new DateTime(
+ "-{$this->atts['length']} {$this->atts['units']}"
+ , $timezone
+ );
+
+ if ( 'minutes' === $this->atts['units'] || 'hours' === $this->atts['units'] ) {
+ $from->setTime( $from->format( 'H' ), $from->format( 'i' ) );
+ }
+
+ if ( 'days' === $this->atts['units'] ) {
+ $from->setTime( $from->format( 'H' ), 0 );
+ }
+
+ if ( 'months' === $this->atts['units'] || 'weeks' === $this->atts['units'] ) {
+ $from->setTime( 0, 0 );
+ }
+
+ } else {
+
+ $from = new DateTime( null, $timezone );
+
+ if ( 'seconds' !== $this->atts['units'] ) {
+ $this->atts['length'] -= 1;
+ }
+
+ $array = array(
+ 'seconds' => true,
+ 'minutes' => true,
+ 'hours' => true,
+ 'days' => true,
+ );
+
+ if (
+ 0 !== $this->atts['length']
+ && isset( $array[ $this->atts['units'] ] )
+ ) {
+ $from->modify( "-{$this->atts['length']} {$this->atts['units']}" );
+ }
+
+ if ( 'minutes' === $this->atts['units'] ) {
+ $from->setTime( $from->format( 'H' ), $from->format( 'i' ) );
+ }
+
+ if ( 'hours' === $this->atts['units'] ) {
+ $from->setTime( $from->format( 'H' ), 0 );
+ }
+
+ if ( 'days' === $this->atts['units'] ) {
+ $from->setTime( 0, 0 );
+ }
+
+ if ( 'months' === $this->atts['units'] ) {
+ $from->setTime( 0, 0 );
+ $from->setDate(
+ $from->format( 'Y' )
+ , $from->format( 'm' ) - $this->atts['length']
+ , 0
+ );
+ }
+
+ if ( 'weeks' === $this->atts['units'] ) {
+
+ $from->setTime( 0, 0 );
+
+ $start_of_week = (int) get_option( 'start_of_week' );
+ $day_of_week = (int) $from->format( 'w' );
+
+ // ISO assumes weeks start on Monday (1). For other days, we have to
+ // check if a new (non-ISO) week has already started, and take that
+ // into account.
+ if ( 1 !== $start_of_week && $day_of_week === $start_of_week ) {
+ $this->atts['length'] -= 1;
+ }
+
+ $from->setISODate(
+ $from->format( 'Y' )
+ , $from->format( 'W' ) - $this->atts['length']
+ , $start_of_week
+ );
+ }
+
+ } // End if ( relative to present ) else {}.
+
+ $args = array(
+ 'points_type' => $this->atts['points_type'],
+ 'limit' => $this->atts['users'],
+ 'user_id__not_in' => wordpoints_get_excluded_users(
+ 'top_users_in_period_widget'
+ ),
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $from, null, $args );
+ $table = new WordPoints_Top_Users_In_Period_Table( $query, 'shortcode' );
+
+ ob_start();
+ $table->display();
+ return ob_get_clean();
+ }
+}
+
+// EOF
diff --git a/src/classes/shortcode/fixed.php b/src/classes/shortcode/fixed.php
new file mode 100644
index 0000000..19cf677
--- /dev/null
+++ b/src/classes/shortcode/fixed.php
@@ -0,0 +1,102 @@
+ 10,
+ 'points_type' => '',
+ 'from' => '',
+ 'from_time' => '00:00',
+ 'to' => '',
+ 'to_time' => '23:59',
+ );
+
+ /**
+ * @since 1.0.0
+ */
+ protected function verify_atts() {
+
+ if ( empty( $this->atts['from'] ) ) {
+ $instance['from'] = current_time( 'Y-m-d' );
+ }
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+ $datetime = "{$this->atts['from']} {$this->atts['from_time']}:00";
+
+ if ( ! wordpoints_top_users_in_period_validate_datetime( $datetime, $timezone ) ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_widget_invalid_from'
+ , esc_html__( 'Please enter a valid From date and time.', 'wordpoints-top-users-in-period' )
+ );
+ }
+
+ if ( ! empty( $this->atts['to'] ) ) {
+
+ $datetime = "{$this->atts['to']} {$this->atts['to_time']}:59";
+
+ if ( ! wordpoints_top_users_in_period_validate_datetime( $datetime, $timezone ) ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_widget_invalid_from'
+ , esc_html__( 'Please enter a valid To date and time.', 'wordpoints-top-users-in-period' )
+ );
+ }
+ }
+
+ return parent::verify_atts();
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function generate() {
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $from = new DateTime(
+ "{$this->atts['from']} {$this->atts['from_time']}:00"
+ , $timezone
+ );
+
+ $to = null;
+
+ if ( ! empty( $this->atts['to'] ) ) {
+ $to = new DateTime(
+ "{$this->atts['to']} {$this->atts['to_time']}:59"
+ , $timezone
+ );
+ }
+
+ $args = array(
+ 'points_type' => $this->atts['points_type'],
+ 'limit' => $this->atts['users'],
+ 'user_id__not_in' => wordpoints_get_excluded_users(
+ 'top_users_in_period_widget'
+ ),
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $from, $to, $args );
+ $table = new WordPoints_Top_Users_In_Period_Table( $query, 'shortcode' );
+
+ ob_start();
+ $table->display();
+ return ob_get_clean();
+ }
+}
+
+// EOF
diff --git a/src/classes/table.php b/src/classes/table.php
new file mode 100644
index 0000000..4a0e35f
--- /dev/null
+++ b/src/classes/table.php
@@ -0,0 +1,319 @@
+query = $query;
+ $this->context = $context;
+ }
+
+ /**
+ * Displays the table.
+ *
+ * @since 1.0.0
+ */
+ public function display() {
+
+ wp_enqueue_style( 'wordpoints-top-users-in-period-table' );
+
+ $top_users = $this->query->get();
+
+ if ( is_wp_error( $top_users ) ) {
+ $this->display_message( __( 'There was an error running the query, please try again in a few moments.', 'wordpoints-top-users-in-period' ), 'error' );
+ return;
+ }
+
+ if ( empty( $top_users ) ) {
+ $this->display_message( __( 'No users received points during this period.', 'wordpoints-top-users-in-period' ), 'info' );
+ return;
+ }
+
+ $this->points_type = $this->get_points_type();
+ $this->column_headers = $this->get_column_headers();
+
+ $extra_classes = $this->get_extra_classes( $top_users );
+
+ ?>
+
+
+
+ _x( '#', 'top users table heading', 'wordpoints-top-users-in-period' ),
+ 'position' => _x( 'Position', 'top users table heading', 'wordpoints-top-users-in-period' ),
+ 'user' => _x( 'User', 'top users table heading', 'wordpoints-top-users-in-period' ),
+ 'points' => _x( 'Points', 'top users table heading', 'wordpoints-top-users-in-period' ),
+ );
+
+ if ( $this->points_type ) {
+
+ $points_type_name = wordpoints_get_points_type_setting(
+ $this->points_type
+ , 'name'
+ );
+
+ if ( ! empty( $points_type_name ) ) {
+ $column_headers['points'] = $points_type_name;
+ }
+ }
+
+ return $column_headers;
+ }
+
+ /**
+ * Displays the column headers for a table.
+ *
+ * @since 1.0.0
+ */
+ protected function display_headers() {
+
+ ?>
+
+
+
+ column_headers['#'] ); ?>
+ column_headers['position'] ); ?>
+ |
+ column_headers['user'] ); ?> |
+ column_headers['points'] ); ?> |
+
+
+ query
+ , $results
+ );
+
+ return $extra_classes;
+ }
+
+ /**
+ * Gets the points type this query is for.
+ *
+ * @since 1.0.0
+ *
+ * @return string|null The points type, or null if not for a particular type.
+ */
+ protected function get_points_type() {
+
+ $points_type = $this->query->get_arg( 'points_type' );
+
+ if ( $this->query->get_arg( 'points_type__compare' ) ) {
+ $points_type = null;
+ }
+
+ return $points_type;
+ }
+}
+
+// EOF
diff --git a/src/classes/widget/dynamic.php b/src/classes/widget/dynamic.php
new file mode 100644
index 0000000..65ebad0
--- /dev/null
+++ b/src/classes/widget/dynamic.php
@@ -0,0 +1,299 @@
+ __( 'Showcase the users who earned the most points in the last month/day/week/etc.', 'wordpoints-top-users-in-period' ),
+ 'wordpoints_hook_slug' => 'top_users_in_period_dynamic',
+ )
+ );
+
+ $this->defaults = array(
+ 'title' => _x(
+ 'Top Users In The Last Day'
+ , 'widget title'
+ , 'wordpoints-top-users-in-period'
+ ),
+ 'points_type' => wordpoints_get_default_points_type(),
+ 'num_users' => 3,
+ 'length_in_units' => 1,
+ 'relative' => 'present',
+ 'units' => 'days',
+ );
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function verify_settings( $instance ) {
+
+ $instance = $this->validate_settings( $instance );
+
+ return parent::verify_settings( $instance );
+ }
+
+ /**
+ * Validate the settings of an instance.
+ *
+ * @since 1.0.0
+ *
+ * @param array $instance The instance to validate.
+ *
+ * @return array The validated settings.
+ */
+ protected function validate_settings( $instance ) {
+
+ if ( ! wordpoints_posint( $instance['length_in_units'] ) ) {
+ $instance['length_in_units'] = $this->defaults['length_in_units'];
+ }
+
+ $options = array( 'present' => true, 'calendar' => true );
+
+ if ( ! isset( $instance['relative'], $options[ $instance['relative'] ] ) ) {
+ $instance['relative'] = $this->defaults['relative'];
+ }
+
+ $options = array(
+ 'seconds' => true,
+ 'minutes' => true,
+ 'hours' => true,
+ 'days' => true,
+ 'weeks' => true,
+ 'months' => true,
+ );
+
+ if ( ! isset( $instance['units'], $options[ $instance['units'] ] ) ) {
+ $instance['units'] = $this->defaults['units'];
+ }
+
+ return $instance;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function widget_body( $instance ) {
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ if ( 'present' === $instance['relative'] ) {
+
+ $from = new DateTime(
+ "-{$instance['length_in_units']} {$instance['units']}"
+ , $timezone
+ );
+
+ if ( 'minutes' === $instance['units'] || 'hours' === $instance['units'] ) {
+ $from->setTime( $from->format( 'H' ), $from->format( 'i' ) );
+ }
+
+ if ( 'days' === $instance['units'] ) {
+ $from->setTime( $from->format( 'H' ), 0 );
+ }
+
+ if ( 'months' === $instance['units'] || 'weeks' === $instance['units'] ) {
+ $from->setTime( 0, 0 );
+ }
+
+ } else {
+
+ $from = new DateTime( null, $timezone );
+
+ if ( 'seconds' !== $instance['units'] ) {
+ $instance['length_in_units'] -= 1;
+ }
+
+ $array = array(
+ 'seconds' => true,
+ 'minutes' => true,
+ 'hours' => true,
+ 'days' => true,
+ );
+
+ if (
+ 0 !== $instance['length_in_units']
+ && isset( $array[ $instance['units'] ] )
+ ) {
+ $from->modify( "-{$instance['length_in_units']} {$instance['units']}" );
+ }
+
+ if ( 'minutes' === $instance['units'] ) {
+ $from->setTime( $from->format( 'H' ), $from->format( 'i' ) );
+ }
+
+ if ( 'hours' === $instance['units'] ) {
+ $from->setTime( $from->format( 'H' ), 0 );
+ }
+
+ if ( 'days' === $instance['units'] ) {
+ $from->setTime( 0, 0 );
+ }
+
+ if ( 'months' === $instance['units'] ) {
+ $from->setTime( 0, 0 );
+ $from->setDate(
+ $from->format( 'Y' )
+ , $from->format( 'm' ) - $instance['length_in_units']
+ , 0
+ );
+ }
+
+ if ( 'weeks' === $instance['units'] ) {
+
+ $from->setTime( 0, 0 );
+
+ $start_of_week = (int) get_option( 'start_of_week' );
+ $day_of_week = (int) $from->format( 'w' );
+
+ // ISO assumes weeks start on Monday (1). For other days, we have to
+ // check if a new (non-ISO) week has already started, and take that
+ // into account.
+ if ( 1 !== $start_of_week && $day_of_week === $start_of_week ) {
+ $instance['length_in_units'] -= 1;
+ }
+
+ $from->setISODate(
+ $from->format( 'Y' )
+ , $from->format( 'W' ) - $instance['length_in_units']
+ , $start_of_week
+ );
+ }
+
+ } // End if ( relative to present ) else {}.
+
+ $args = array(
+ 'points_type' => $instance['points_type'],
+ 'limit' => $instance['num_users'],
+ 'user_id__not_in' => wordpoints_get_excluded_users(
+ 'top_users_in_period_widget'
+ ),
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $from, null, $args );
+ $table = new WordPoints_Top_Users_In_Period_Table( $query, 'widget' );
+ $table->display();
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function update( $new_instance, $old_instance ) {
+
+ parent::update( $new_instance, $old_instance );
+
+ $this->instance = $this->validate_settings( $this->instance );
+
+ return $this->instance;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function form( $instance ) {
+
+ wp_enqueue_style( 'wordpoints-top-users-in-period-widget-settings' );
+
+ parent::form( $instance );
+
+ ?>
+
+
+
+ __( 'Showcase the users who earned the most points between two fixed dates.', 'wordpoints-top-users-in-period' ),
+ 'wordpoints_hook_slug' => 'top_users_in_period_fixed',
+ )
+ );
+
+ $this->defaults = array(
+ 'title' => _x( 'Top Users', 'widget title', 'wordpoints-top-users-in-period' ),
+ 'points_type' => wordpoints_get_default_points_type(),
+ 'num_users' => 3,
+ 'from' => current_time( 'Y-m-d' ),
+ 'from_time' => '00:00',
+ 'to' => '',
+ 'to_time' => '23:59',
+ );
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function verify_settings( $instance ) {
+
+ if ( empty( $instance['from'] ) ) {
+ $instance['from'] = $this->defaults['from'];
+ }
+
+ if ( empty( $instance['from_time'] ) ) {
+ $instance['from_time'] = $this->defaults['from_time'];
+ }
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+ $datetime = "{$instance['from']} {$instance['from_time']}:00";
+
+ if ( ! wordpoints_top_users_in_period_validate_datetime( $datetime, $timezone ) ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_widget_invalid_from'
+ , esc_html__( 'Please enter a valid From date and time.', 'wordpoints-top-users-in-period' )
+ );
+ }
+
+ if ( ! empty( $instance['to'] ) ) {
+
+ if ( empty( $instance['to_time'] ) ) {
+ $instance['to_time'] = $this->defaults['to_time'];
+ }
+
+ $datetime = "{$instance['to']} {$instance['to_time']}:59";
+
+ if ( ! wordpoints_top_users_in_period_validate_datetime( $datetime, $timezone ) ) {
+ return new WP_Error(
+ 'wordpoints_top_users_in_period_widget_invalid_from'
+ , esc_html__( 'Please enter a valid To date and time.', 'wordpoints-top-users-in-period' )
+ );
+ }
+ }
+
+ return parent::verify_settings( $instance );
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ protected function widget_body( $instance ) {
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $from = new DateTime(
+ "{$instance['from']} {$instance['from_time']}:00"
+ , $timezone
+ );
+
+ $to = null;
+
+ if ( ! empty( $instance['to'] ) ) {
+ $to = new DateTime(
+ "{$instance['to']} {$instance['to_time']}:59"
+ , $timezone
+ );
+ }
+
+ $args = array(
+ 'points_type' => $instance['points_type'],
+ 'limit' => $instance['num_users'],
+ 'user_id__not_in' => wordpoints_get_excluded_users(
+ 'top_users_in_period_widget'
+ ),
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $from, $to, $args );
+ $table = new WordPoints_Top_Users_In_Period_Table( $query, 'widget' );
+ $table->display();
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function update( $new_instance, $old_instance ) {
+
+ parent::update( $new_instance, $old_instance );
+
+ $this->instance['from'] = sanitize_text_field( $this->instance['from'] );
+ $this->instance['from_time'] = sanitize_text_field( $this->instance['from_time'] );
+ $this->instance['to'] = sanitize_text_field( $this->instance['to'] );
+ $this->instance['to_time'] = sanitize_text_field( $this->instance['to_time'] );
+
+ return $this->instance;
+ }
+
+ /**
+ * @since 1.0.0
+ */
+ public function form( $instance ) {
+
+ wp_enqueue_style( 'wordpoints-top-users-in-period-widget-settings' );
+ wp_enqueue_style( 'wordpoints-top-users-in-period-datepicker' );
+ wp_enqueue_script( 'wordpoints-top-users-in-period-datepicker' );
+
+ parent::form( $instance );
+
+ ?>
+
+
+
+ get_sub_app( 'admin' )
+ ->get_sub_app( 'screen' );
+
+ // Hooks page.
+ $id = add_submenu_page(
+ $wordpoints_menu
+ , __( 'WordPoints — Top Users', 'wordpoints-top-users-in-period' )
+ , __( 'Top Users', 'wordpoints-top-users-in-period' )
+ , 'manage_options'
+ , 'wordpoints_top_users_in_period'
+ , array( $admin_screens, 'display' )
+ );
+
+ if ( $id ) {
+ $admin_screens->register( $id, 'WordPoints_Top_Users_In_Period_Admin_Screen' );
+ }
+}
+
+// EOF
diff --git a/src/includes/class-un-installer.php b/src/includes/class-un-installer.php
new file mode 100644
index 0000000..2560195
--- /dev/null
+++ b/src/includes/class-un-installer.php
@@ -0,0 +1,52 @@
+ array(
+ 'tables' => array(
+ 'wordpoints_top_users_in_period_blocks' => '
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ block_type VARCHAR(32) NOT NULL,
+ start_date DATETIME NOT NULL,
+ end_date DATETIME NOT NULL,
+ query_signature CHAR(64) NOT NULL,
+ status VARCHAR(10) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY block_signature (block_type,query_signature,start_date)',
+ 'wordpoints_top_users_in_period_block_logs' => '
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ block_id BIGINT(20) UNSIGNED NOT NULL,
+ user_id BIGINT(20) UNSIGNED NOT NULL,
+ points BIGINT(20) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY block_user_id (user_id,block_id)',
+ ),
+ ),
+ );
+}
+
+return 'WordPoints_Top_Users_In_Period_Un_Installer';
+
+// EOF
diff --git a/src/includes/functions.php b/src/includes/functions.php
new file mode 100644
index 0000000..4204ffb
--- /dev/null
+++ b/src/includes/functions.php
@@ -0,0 +1,253 @@
+add_data( $rtl_style, 'rtl', 'replace' );
+
+ if ( $suffix ) {
+ $styles->add_data( $rtl_style, 'suffix', $suffix );
+ }
+ }
+}
+
+/**
+ * Register the widgets.
+ *
+ * @since 1.0.0
+ *
+ * @action widgets_init
+ */
+function wordpoints_top_users_in_period_register_widgets() {
+
+ register_widget( 'WordPoints_Top_Users_In_Period_Widget_Dynamic' );
+ register_widget( 'WordPoints_Top_Users_In_Period_Widget_Fixed' );
+}
+
+/**
+ * Creates a `DateTimeZone` object for the site's timezone.
+ *
+ * This function determines the site timezone as follows:
+ *
+ * - If the site uses a timezone identifier (i.e., 'timezone_string' option is set),
+ * that is used.
+ * - If that's not set, we make up an identifier based on the 'gmt_offset'.
+ * - If the GMT offset is 0, or the identifier is invalid, UTC is used.
+ *
+ * @see https://wordpress.stackexchange.com/a/198453/27757
+ * @see https://us.php.net/manual/en/timezones.others.php
+ *
+ * @return DateTimeZone The site's timezone.
+ */
+function wordpoints_top_users_in_period_get_site_timezone() {
+
+ $timezone_string = get_option( 'timezone_string' );
+
+ // A direct offset may be used instead of a timezone identifier.
+ if ( empty( $timezone_string ) ) {
+
+ $offset = get_option( 'gmt_offset' );
+
+ if ( empty( $offset ) ) {
+
+ $timezone_string = 'UTC';
+
+ } else {
+
+ $hours = (int) $offset;
+ $minutes = ( $offset - floor( $offset ) ) * 60;
+ $timezone_string = sprintf( '%+03d:%02d', $hours, $minutes );
+ }
+ }
+
+ // The offsets in particular do not work prior to PHP 5.5.
+ try {
+ $timezone = new DateTimeZone( $timezone_string );
+ } catch ( Exception $e ) {
+ $timezone = new DateTimeZone( 'UTC' );
+ }
+
+ return $timezone;
+}
+
+/**
+ * Validate a datetime string.
+ *
+ * @since 1.0.0
+ *
+ * @param string $datetime The datetime string.
+ * @param DateTimeZone $timezone The timezone to use.
+ *
+ * @return bool Whether the datetime is valid.
+ */
+function wordpoints_top_users_in_period_validate_datetime( $datetime, $timezone ) {
+
+ try {
+ new DateTime( $datetime, $timezone );
+ } catch ( Exception $e ) {
+ return false;
+ }
+
+ // Requires PHP 5.3+.
+ if ( ! function_exists( 'DateTime::getLastErrors' ) ) {
+ return true;
+ }
+
+ $errors = DateTime::getLastErrors();
+
+ if ( 0 !== $errors['error_count'] || 0 !== $errors['warning_count'] ) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Register Top Users In Period module app when the Modules registry is initialized.
+ *
+ * @since 1.0.0
+ *
+ * @WordPress\action wordpoints_init_app-modules
+ *
+ * @param WordPoints_App $modules The modules app.
+ */
+function wordpoints_top_users_in_period_modules_app_init( $modules ) {
+
+ $apps = $modules->sub_apps();
+
+ $apps->register( 'top_users_in_period', 'WordPoints_App' );
+}
+
+/**
+ * Register sub apps when the Top Users In Period app is initialized.
+ *
+ * @since 1.0.0
+ *
+ * @WordPress\action wordpoints_init_app-modules-top_users_in_period
+ *
+ * @param WordPoints_App $app The Top Users In Period app.
+ */
+function wordpoints_top_users_in_period_apps_init( $app ) {
+
+ $apps = $app->sub_apps();
+
+ $apps->register( 'block_types', 'WordPoints_Class_Registry' );
+ $apps->register( 'query_caches', 'WordPoints_Class_Registry' );
+}
+
+/**
+ * Register block types when the registry is initialized.
+ *
+ * @since 1.0.0
+ *
+ * @WordPress\action wordpoints_init_app_registry-modules-top_users_in_period-block_types
+ *
+ * @param WordPoints_Class_RegistryI $block_types The block types registry.
+ */
+function wordpoints_top_users_in_period_block_types_init( $block_types ) {
+
+ $block_types->register( 'week_in_seconds', 'WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds' );
+}
+
+/**
+ * Register query caches when the registry is initialized.
+ *
+ * @since 1.0.0
+ *
+ * @WordPress\action wordpoints_init_app_registry-modules-top_users_in_period-query_caches
+ *
+ * @param WordPoints_Class_RegistryI $query_caches The query caches registry.
+ */
+function wordpoints_top_users_in_period_query_caches_init( $query_caches ) {
+
+ $query_caches->register( 'transients', 'WordPoints_Top_Users_In_Period_Query_Cache_Transients' );
+}
+
+/**
+ * Flushes the query caches in reference to a particular points log.
+ *
+ * @since 1.0.0
+ *
+ * @WordPress\action wordpoints_points_altered Only really needed when the points are
+ * actually logged, but we can't hook into that action due to a
+ * bug: https://github.com/WordPoints/wordpoints/issues/651
+ */
+function wordpoints_top_users_in_period_query_caches_flush_for_log(
+ $user_id,
+ $points,
+ $points_type,
+ $log_type
+) {
+
+ $args = array(
+ 'user_id' => $user_id,
+ 'points_type' => $points_type,
+ 'log_type' => $log_type,
+ );
+
+ if ( is_multisite() ) {
+ $args['blog_id'] = get_current_blog_id();
+ $args['site_id'] = get_current_network_id();
+ }
+
+ $flusher = new WordPoints_Top_Users_In_Period_Query_Cache_Flusher( $args );
+ $flusher->flush();
+}
+
+// EOF
diff --git a/src/languages/wordpoints-top-users-in-period.pot b/src/languages/wordpoints-top-users-in-period.pot
new file mode 100644
index 0000000..8ae4e9b
--- /dev/null
+++ b/src/languages/wordpoints-top-users-in-period.pot
@@ -0,0 +1,233 @@
+# Copyright (C) 2017 J.D. Grimes
+# This file is distributed under the same license as the WordPoints Top Users In Period package.
+msgid ""
+msgstr ""
+"Project-Id-Version: WordPoints Top Users In Period 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-05-08 13:21:53+00:00\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+
+#: classes/admin/screen.php:21 includes/admin/functions.php:31
+msgid "Top Users"
+msgstr ""
+
+#: classes/admin/screen.php:39
+msgid "From date (inclusive):"
+msgstr ""
+
+#: classes/admin/screen.php:42
+msgid "From (inclusive):"
+msgstr ""
+
+#: classes/admin/screen.php:48 classes/widget/fixed.php:160
+msgid "Date"
+msgstr ""
+
+#: classes/admin/screen.php:54
+msgid "From time (inclusive):"
+msgstr ""
+
+#: classes/admin/screen.php:59 classes/admin/screen.php:85
+#: classes/widget/fixed.php:172 classes/widget/fixed.php:202
+msgid "Time"
+msgstr ""
+
+#: classes/admin/screen.php:65
+msgid "To date (inclusive) (default: now):"
+msgstr ""
+
+#: classes/admin/screen.php:68
+msgid "To (inclusive):"
+msgstr ""
+
+#: classes/admin/screen.php:74 classes/widget/fixed.php:190
+msgid "Date (default: now)"
+msgstr ""
+
+#: classes/admin/screen.php:80
+msgid "To time (inclusive):"
+msgstr ""
+
+#: classes/admin/screen.php:90
+msgid "Points Type:"
+msgstr ""
+
+#: classes/admin/screen.php:97
+msgctxt "points types"
+msgid "All"
+msgstr ""
+
+#: classes/admin/screen.php:105
+msgid "Show Users"
+msgstr ""
+
+#: classes/query.php:243
+msgid "End date cannot come before start date."
+msgstr ""
+
+#: classes/shortcode/fixed.php:45 classes/widget/fixed.php:62
+msgid "Please enter a valid From date and time."
+msgstr ""
+
+#: classes/shortcode/fixed.php:56 classes/widget/fixed.php:77
+msgid "Please enter a valid To date and time."
+msgstr ""
+
+#: classes/table.php:82
+msgid ""
+"There was an error running the query, please try again in a few moments."
+msgstr ""
+
+#: classes/table.php:87
+msgid "No users received points during this period."
+msgstr ""
+
+#: classes/table.php:221
+msgctxt "top users table heading"
+msgid "#"
+msgstr ""
+
+#: classes/table.php:222
+msgctxt "top users table heading"
+msgid "Position"
+msgstr ""
+
+#: classes/table.php:223
+msgctxt "top users table heading"
+msgid "User"
+msgstr ""
+
+#: classes/table.php:224
+msgctxt "top users table heading"
+msgid "Points"
+msgstr ""
+
+#: classes/widget/dynamic.php:25
+msgctxt "widget name"
+msgid "Top Users In Dynamic Period"
+msgstr ""
+
+#: classes/widget/dynamic.php:27
+msgid ""
+"Showcase the users who earned the most points in the last month/day/week/etc."
+msgstr ""
+
+#: classes/widget/dynamic.php:33
+msgctxt "widget title"
+msgid "Top Users In The Last Day"
+msgstr ""
+
+#: classes/widget/dynamic.php:224
+msgid "Show top users based on the last"
+msgstr ""
+
+#: classes/widget/dynamic.php:228
+msgid "Time Units"
+msgstr ""
+
+#: classes/widget/dynamic.php:235
+msgid "Seconds"
+msgstr ""
+
+#: classes/widget/dynamic.php:238
+msgid "Minutes"
+msgstr ""
+
+#: classes/widget/dynamic.php:241
+msgid "Hours"
+msgstr ""
+
+#: classes/widget/dynamic.php:244
+msgid "Days"
+msgstr ""
+
+#: classes/widget/dynamic.php:247
+msgid "Weeks"
+msgstr ""
+
+#: classes/widget/dynamic.php:250
+msgid "Months"
+msgstr ""
+
+#: classes/widget/dynamic.php:256
+msgid "Number of units"
+msgstr ""
+
+#: classes/widget/dynamic.php:275
+msgid ""
+"Count back from the present (for example, one week would mean during the "
+"last seven days)."
+msgstr ""
+
+#: classes/widget/dynamic.php:287
+msgid ""
+"Calculate relative to the calendar (for example, one week would mean during "
+"the current calendar week)."
+msgstr ""
+
+#: classes/widget/fixed.php:25
+msgctxt "widget name"
+msgid "Top Users In Fixed Period"
+msgstr ""
+
+#: classes/widget/fixed.php:27
+msgid "Showcase the users who earned the most points between two fixed dates."
+msgstr ""
+
+#: classes/widget/fixed.php:33
+msgctxt "widget title"
+msgid "Top Users"
+msgstr ""
+
+#: classes/widget/fixed.php:151
+msgid "From date (inclusive)"
+msgstr ""
+
+#: classes/widget/fixed.php:154
+msgid "From (inclusive)"
+msgstr ""
+
+#: classes/widget/fixed.php:166
+msgid "From time (inclusive)"
+msgstr ""
+
+#: classes/widget/fixed.php:181
+msgid "To date (inclusive) (default: now)"
+msgstr ""
+
+#: classes/widget/fixed.php:184
+msgid "To (inclusive)"
+msgstr ""
+
+#: classes/widget/fixed.php:196
+msgid "To time (inclusive)"
+msgstr ""
+
+#: includes/admin/functions.php:30
+msgid "WordPoints — Top Users"
+msgstr ""
+
+#. Description of the module
+msgid "Display the top points earners within a given period of time."
+msgstr ""
+
+#. Author of the module
+msgid "J.D. Grimes"
+msgstr ""
+
+#. Author URI of the module
+msgid "https://wordpoints.org/"
+msgstr ""
+
+#. Module Name of the module
+msgid "Top Users In Period"
+msgstr ""
+
+#. Module URI of the module
+msgid "https://wordpoints.org/modules/top-users-in-period/"
+msgstr ""
diff --git a/src/top-users-in-period.php b/src/top-users-in-period.php
new file mode 100644
index 0000000..99505cf
--- /dev/null
+++ b/src/top-users-in-period.php
@@ -0,0 +1,79 @@
+
+ * @license GPLv2+
+ */
+
+WordPoints_Modules::register(
+ '
+ Module Name: Top Users In Period
+ Author: J.D. Grimes
+ Author URI: https://wordpoints.org/
+ Module URI: https://wordpoints.org/modules/top-users-in-period/
+ Version: 1.0.0
+ License: GPLv2+
+ Description: Display the top points earners within a given period of time.
+ Text Domain: wordpoints-top-users-in-period
+ Domain Path: /languages
+ Channel: wordpoints.org
+ ID: 1058
+ Namespace: Top_Users_In_Period
+ '
+ , __FILE__
+);
+
+WordPoints_Class_Autoloader::register_dir( dirname( __FILE__ ) . '/classes/' );
+
+/**
+ * Contains the modules main functions.
+ *
+ * @since 1.0.0
+ */
+require_once dirname( __FILE__ ) . '/includes/functions.php';
+
+/**
+ * Hooks up the actions and filters for the module.
+ *
+ * @since 1.0.0
+ */
+require_once dirname( __FILE__ ) . '/includes/actions.php';
+
+if ( is_admin() ) {
+ /**
+ * Admin-side functions.
+ *
+ * @since 1.0.0
+ */
+ require_once dirname( __FILE__ ) . '/includes/admin/functions.php';
+
+ /**
+ * Admin-side actions and filters.
+ *
+ * @since 1.0.0
+ */
+ require_once dirname( __FILE__ ) . '/includes/admin/actions.php';
+}
+
+// EOF
diff --git a/tests/codeception/_support/AcceptanceTester.php b/tests/codeception/_support/AcceptanceTester.php
new file mode 100644
index 0000000..8b786db
--- /dev/null
+++ b/tests/codeception/_support/AcceptanceTester.php
@@ -0,0 +1,19 @@
+assertTableExists( $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks' );
+ $this->assertTableExists( $wpdb->base_prefix . 'wordpoints_top_users_in_period_block_logs' );
+
+ /*
+ * Now, test that it uninstalls itself properly.
+ */
+
+ $this->uninstall();
+
+ // Check that everything with this module's prefix has been uninstalled.
+ $this->assertUninstalledPrefix( 'wordpoints_top_users_in_period' );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/block/logs/query.php b/tests/phpunit/tests/classes/block/logs/query.php
new file mode 100644
index 0000000..2111703
--- /dev/null
+++ b/tests/phpunit/tests/classes/block/logs/query.php
@@ -0,0 +1,128 @@
+assertSame( 'total', $query->get_arg( 'order_by' ) );
+ }
+
+ /**
+ * Test that the fields query arg is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Block_Logs_Query::prepare_select
+ */
+ public function test_fields_not_supported() {
+
+ $query = new WordPoints_Top_Users_In_Period_Block_Logs_Query(
+ array( 'fields' => 'total' )
+ );
+
+ $query->get_sql();
+ }
+
+ /**
+ * Test that the fields query arg is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Block_Logs_Query::prepare_select
+ */
+ public function test_fields_not_supported_set_args() {
+
+ $query = new WordPoints_Top_Users_In_Period_Block_Logs_Query();
+ $query->set_args( array( 'fields' => 'total' ) );
+
+ $query->get_sql();
+ }
+
+ /**
+ * Test that the results are grouped by user.
+ *
+ * @since 1.0.0
+ */
+ public function test_results() {
+
+ global $wpdb;
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_block_logs'
+ , array(
+ 'block_id' => '1',
+ 'user_id' => '5',
+ 'points' => '214',
+ )
+ );
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_block_logs'
+ , array(
+ 'block_id' => '2',
+ 'user_id' => '4',
+ 'points' => '57',
+ )
+ );
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_block_logs'
+ , array(
+ 'block_id' => '3',
+ 'user_id' => '5',
+ 'points' => '5',
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Block_Logs_Query();
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+ $this->assertSameProperties(
+ (object) array( 'total' => '219', 'user_id' => '5' )
+ , $results[0]
+ );
+ $this->assertSameProperties(
+ (object) array( 'total' => '57', 'user_id' => '4' )
+ , $results[1]
+ );
+
+ // Conditions on the total.
+ $query = new WordPoints_Top_Users_In_Period_Block_Logs_Query(
+ array( 'total' => 100, 'total__compare' => '>=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+ $this->assertSameProperties(
+ (object) array( 'total' => '219', 'user_id' => '5' )
+ , $results[0]
+ );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/block/type/week/in/seconds.php b/tests/phpunit/tests/classes/block/type/week/in/seconds.php
new file mode 100644
index 0000000..dd67044
--- /dev/null
+++ b/tests/phpunit/tests/classes/block/type/week/in/seconds.php
@@ -0,0 +1,54 @@
+assertSame( 'test', $block_type->get_slug() );
+ }
+
+ /**
+ * Tests getting the start and end times for a particular block.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_block_info() {
+
+ $block_type = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $timestamp = 1490908830;
+
+ $info = $block_type->get_block_info( $timestamp );
+
+ $this->assertSame( 1490832000, $info['start'] );
+ $this->assertSame( 1491436799, $info['end'] );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/blocks/query.php b/tests/phpunit/tests/classes/blocks/query.php
new file mode 100644
index 0000000..a492d50
--- /dev/null
+++ b/tests/phpunit/tests/classes/blocks/query.php
@@ -0,0 +1,65 @@
+assertSame( 'start_date', $query->get_arg( 'order_by' ) );
+ }
+
+ /**
+ * Test that query actually works.
+ *
+ * @since 1.0.0
+ */
+ public function test_results() {
+
+ global $wpdb;
+
+ $block = array(
+ 'block_type' => 'weekly',
+ 'start_date' => '2017-03-21 10:25:32',
+ 'end_date' => '2017-03-28 10:25:32',
+ 'query_signature' => '111111111111',
+ 'status' => 'draft',
+ );
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , $block
+ );
+
+ $block['id'] = (string) $wpdb->insert_id;
+
+ $query = new WordPoints_Top_Users_In_Period_Blocks_Query();
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+ $this->assertSameProperties( (object) $block, $results[0] );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/points/logs/query.php b/tests/phpunit/tests/classes/points/logs/query.php
new file mode 100644
index 0000000..88fa777
--- /dev/null
+++ b/tests/phpunit/tests/classes/points/logs/query.php
@@ -0,0 +1,113 @@
+assertSame( 'total', $query->get_arg( 'order_by' ) );
+ }
+
+ /**
+ * Test that the fields query arg is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Points_Logs_Query::prepare_select
+ */
+ public function test_fields_not_supported() {
+
+ $query = new WordPoints_Top_Users_In_Period_Points_Logs_Query(
+ array( 'fields' => 'total' )
+ );
+
+ $query->get_sql();
+ }
+
+ /**
+ * Test that the fields query arg is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Points_Logs_Query::prepare_select
+ */
+ public function test_fields_not_supported_set_args() {
+
+ $query = new WordPoints_Top_Users_In_Period_Points_Logs_Query();
+ $query->set_args( array( 'fields' => 'total' ) );
+
+ $query->get_sql();
+ }
+
+ /**
+ * Test that the results are grouped by user.
+ *
+ * @since 1.0.0
+ */
+ public function test_results() {
+
+ $user_id_1 = $this->factory->user->create();
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id_1, 'points' => 50 )
+ );
+
+ $user_id_2 = $this->factory->user->create();
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id_2, 'points' => 6 )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id_1, 'points' => 4 )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Points_Logs_Query();
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+ $this->assertSameProperties(
+ (object) array( 'total' => '54', 'user_id' => (string) $user_id_1 )
+ , $results[0]
+ );
+ $this->assertSameProperties(
+ (object) array( 'total' => '6', 'user_id' => (string) $user_id_2 )
+ , $results[1]
+ );
+
+ // Conditions on the total.
+ $query = new WordPoints_Top_Users_In_Period_Points_Logs_Query(
+ array( 'total' => 50, 'total__compare' => '>=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+ $this->assertSameProperties(
+ (object) array( 'total' => '54', 'user_id' => (string) $user_id_1 )
+ , $results[0]
+ );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/query.php b/tests/phpunit/tests/classes/query.php
new file mode 100644
index 0000000..0b7c397
--- /dev/null
+++ b/tests/phpunit/tests/classes/query.php
@@ -0,0 +1,3002 @@
+get_start_date();
+
+ $this->assertNotSame( $start_date, $query_start_date );
+ $this->assertSame(
+ $start_date->format( 'U' )
+ , $query_start_date->format( 'U' )
+ );
+ }
+
+ /**
+ * Tests getting the end date.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_end_date() {
+
+ $start_date = new DateTime( '-2 months' );
+ $end_date = new DateTime( '-1 month' );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+
+ $query_end_date = $query->get_end_date();
+
+ $this->assertNotSame( $end_date, $query_end_date );
+ $this->assertSame(
+ $end_date->format( 'U' )
+ , $query_end_date->format( 'U' )
+ );
+ }
+
+ /**
+ * Tests getting the default end date.
+ *
+ * @since 1.0.0
+ */
+ public function test_default_end_date() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-2 months' )
+ );
+
+ $this->assertSame(
+ current_time( 'timestamp', true )
+ , (int) $query->get_end_date()->format( 'U' )
+ );
+ }
+
+ /**
+ * Tests that an error is returned when the start is after the end.
+ *
+ * @since 1.0.0
+ */
+ public function test_start_after_end_date() {
+
+ $start_date = new DateTime( '-1 months' );
+ $end_date = new DateTime( '-2 month' );
+
+ $this->listen_for_filter( 'wordpoints_top_user_in_period_query_cache' );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+
+ $this->assertWPError( $query->get() );
+
+ $this->assertSame(
+ 0
+ , $this->filter_was_called( 'wordpoints_top_user_in_period_query_cache' )
+ );
+ }
+
+ /**
+ * Tests getting the default args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_get_args_defaults() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'text__compare' => 'LIKE',
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests getting the default args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_get_args_defaults_multisite() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'start' => 0,
+ 'order' => 'DESC',
+ 'order_by' => 'total',
+ 'text__compare' => 'LIKE',
+ 'blog_id' => '1',
+ 'site_id' => '1',
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests getting the args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_get_args() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array( 'start' => 10, 'limit' => 10 )
+ );
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'text__compare' => 'LIKE',
+ 'start' => 10,
+ 'limit' => 10,
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned, removing unnecessary '*__compare' args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_cleans_args_removes_unused_compare() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'user_id' => 1,
+ 'user_id__compare' => '=', // Kept.
+ 'text__compare' => 'NOT EXISTS', // Kept.
+ 'total__compare' => '!=', // Removed.
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'user_id' => 1,
+ 'user_id__compare' => '=',
+ 'text__compare' => 'NOT EXISTS',
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned, removing unnecessary '*__in' args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_cleans_args_removes_unused_in() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'user_id' => 1,
+ 'user_id__in' => array( 2, 3, 4 ),
+ 'total' => 100,
+ 'total__not_in' => array( 50, 10 ),
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'user_id' => 1,
+ 'total' => 100,
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned, sorting '*__in' and '*__not_in' args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_cleans_args_sorts_in() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'user_id__in' => array( 2, 4, 3 ),
+ 'total__not_in' => array( 50, 10 ),
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'user_id__in' => array( 2, 3, 4 ),
+ 'total__not_in' => array( 10, 50 ),
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned, unique '*__in' and '*__not_in' args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_cleans_args_unique_in() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'user_id__in' => array( 2, 4, 3, 2, 3 ),
+ 'total__not_in' => array( 50, 10, 10 ),
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'user_id__in' => array( 2, 3, 4 ),
+ 'total__not_in' => array( 10, 50 ),
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned, single '*__in' and '*__not_in' args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_cleans_args_once_in() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'user_id__in' => array( 2 ),
+ 'total__not_in' => array( 10, 10 ),
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'user_id' => 2,
+ 'total' => 10,
+ 'total__compare' => '!=',
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_cleans_args_type() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'user_id' => '5',
+ 'total__not_in' => array( '10', 10, '5' ),
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'user_id' => 5,
+ 'total__not_in' => array( 5, 10 ),
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests that the args are cleaned.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_cleans_args_type_multisite() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , array(
+ 'site_id' => '5',
+ 'blog_id__in' => array( '10', 10, '5' ),
+ )
+ );
+
+ $query->get();
+
+ $this->assertSameSetsWithIndex(
+ array(
+ 'order_by' => 'total',
+ 'order' => 'DESC',
+ 'start' => 0,
+ 'site_id' => 5,
+ 'blog_id__in' => array( 5, 10 ),
+ )
+ , $query->get_args()
+ );
+ }
+
+ /**
+ * Tests checking if a query is network scope.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ *
+ * @dataProvider data_provider_network_queries
+ *
+ * @param array $args The query args.
+ */
+ public function test_is_network_scope( $args ) {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , $args
+ );
+
+ $this->assertTrue( $query->is_network_scope() );
+ }
+
+ /**
+ * Data provider for network queries.
+ *
+ * @since 1.0.0
+ *
+ * @return array Network queries.
+ */
+ public function data_provider_network_queries() {
+ return array(
+ 'no_blog_id' => array( array() ),
+ 'different_blog_id' => array( array( 'blog_id' => 2 ) ),
+ 'blog_id_not_=' => array(
+ array( 'blog_id' => 1, 'blog_id__compare' => '!=' ),
+ ),
+ );
+ }
+
+ /**
+ * Tests checking if a query is network scope.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ *
+ * @dataProvider data_provider_per_site_queries
+ *
+ * @param array $args The query args.
+ */
+ public function test_is_network_scope_per_site( $args ) {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , $args
+ );
+
+ $this->assertFalse( $query->is_network_scope() );
+ }
+
+ /**
+ * Data provider for per-site queries.
+ *
+ * @since 1.0.0
+ *
+ * @return array Per-site queries.
+ */
+ public function data_provider_per_site_queries() {
+ return array(
+ 'blog_id' => array( array( 'blog_id' => 1 ) ),
+ 'blog_id_=' => array(
+ array( 'blog_id' => 1, 'blog_id__compare' => '=' ),
+ ),
+ );
+ }
+
+ /**
+ * Tests checking if a query is network scope.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ *
+ * @dataProvider data_provider_per_site_queries
+ *
+ * @param array $args The query args.
+ */
+ public function test_is_network_scope_not_multisite( $args ) {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , null
+ , $args
+ );
+
+ $this->assertFalse( $query->is_network_scope() );
+ }
+
+ /**
+ * Tests that the count function is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Query::count
+ */
+ public function test_count() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertFalse( $query->count() );
+ }
+
+ /**
+ * Tests that the method parameter is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Query::get
+ */
+ public function test_get_method() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( array(), $query->get( 'results' ) );
+ }
+
+ /**
+ * Tests that the query type parameter is not supported.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Query::get_sql
+ */
+ public function test_get_sql_query_type() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertInternalType( 'string', $query->get_sql( 'SELECT' ) );
+ }
+
+ /**
+ * Tests that the cache is checked.
+ *
+ * @since 1.0.0
+ */
+ public function test_checks_cache() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'mock'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $mock = new WordPoints_PHPUnit_Mock_Filter( 'mock' );
+ $mock->add_filter( 'wordpoints_top_user_in_period_query_cache' );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( $cache, $query->get() );
+
+ $this->assertSame( 1, $mock->call_count );
+ }
+
+ /**
+ * Tests that the cache is set with the query results.
+ *
+ * @since 1.0.0
+ */
+ public function test_checks_cache_open_ended() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'transients'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( array(), $query->get() );
+ $this->assertSame(
+ array()
+ , WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Tests that the cache is added to the index.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ */
+ public function test_adds_to_cache_index() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'mock'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $mock = new WordPoints_PHPUnit_Mock_Filter( 'mock' );
+ $mock->add_filter( 'wordpoints_top_user_in_period_query_cache' );
+
+ $start_date = new DateTime( '-1 months' );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date );
+
+ $this->assertSame( $cache, $query->get() );
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $this->assertSame(
+ array(
+ '3d13afbe3e05f625ab72cc2cb1619af40921a833c545520b31c550d39a90aab4' => array(
+ 'args' => array(
+ 'order' => 'DESC',
+ 'order_by' => 'total',
+ 'start' => 0,
+ ),
+ 'caches' => array(
+ 'mock' => array(
+ $start_date->format( 'U' ) => true,
+ ),
+ ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests that the cache is not added to the index when an end date is set.
+ *
+ * @since 1.0.0
+ */
+ public function test_adds_to_cache_index_has_end_date() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'mock'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $mock = new WordPoints_PHPUnit_Mock_Filter( 'mock' );
+ $mock->add_filter( 'wordpoints_top_user_in_period_query_cache' );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ , new DateTime()
+ );
+
+ $this->assertSame( $cache, $query->get() );
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $this->assertSame( array(), $index->get() );
+ }
+
+ /**
+ * Tests that the cache is added to the network index for network queries.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_adds_to_cache_index_network() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'mock'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $mock = new WordPoints_PHPUnit_Mock_Filter( 'mock' );
+ $mock->add_filter( 'wordpoints_top_user_in_period_query_cache' );
+
+ $start_date = new DateTime( '-1 months' );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , null
+ , array( 'blog_id' => 5 )
+ );
+
+ $this->assertSame( $cache, $query->get() );
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $this->assertSame( array(), $index->get() );
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index( true );
+
+ $this->assertSame(
+ array(
+ 'd2b248f5c56d245046870436eb4815aa7d4ab15f87f2af364cb12866b15f6381' => array(
+ 'args' => array(
+ 'blog_id' => 5,
+ 'order' => 'DESC',
+ 'order_by' => 'total',
+ 'site_id' => 1,
+ 'start' => 0,
+ ),
+ 'caches' => array(
+ 'mock' => array(
+ $start_date->format( 'U' ) => true,
+ ),
+ ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests that the cache is not used if it null.
+ *
+ * @since 1.0.0
+ */
+ public function test_cache_null() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'transients'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( array(), $query->get() );
+ }
+
+ /**
+ * Tests that the cache is set with the query results.
+ *
+ * @since 1.0.0
+ */
+ public function test_cache_set() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'transients'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( array(), $query->get() );
+ $this->assertSame(
+ array()
+ , WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Tests that the cache is not set if there is an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_cache_not_set_if_error() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'transients'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_both' )
+ , array( new DateTime( '-1 months' ) )
+ );
+
+ $stub->method( 'get_sql_for_both' )
+ ->willReturn( new WP_Error() );
+
+ $this->assertWPError( $stub->get() );
+ $this->assertFalse(
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Tests that it doesn't use the block logs when the period is too short.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_not_use_block_logs() {
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_points_logs' )
+ , array( new DateTime( '-1 day' ) )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_points_logs' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that it uses the block logs when the period is longer.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs() {
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_both' )
+ , array( new DateTime( '-1 month' ) )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_both' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that the use of block logs can be enabled via the filter.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs_filter() {
+
+ add_filter(
+ 'wordpoints_top_users_in_period_query_use_blocks'
+ , '__return_true'
+ );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_both' )
+ , array( new DateTime( '-1 day' ) )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_both' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that the use of block logs can be disabled via the filter.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs_filter_disable() {
+
+ add_filter(
+ 'wordpoints_top_users_in_period_query_use_blocks'
+ , '__return_false'
+ );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_points_logs' )
+ , array( new DateTime( '-1 month' ) )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_points_logs' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that it uses the block logs when the period fits one block exactly.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs_two_blocks() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $second_info = $block->get_block_info( current_time( 'timestamp' ) );
+ $first_info = $block->get_block_info( $second_info['start'] - 1 );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_both' )
+ , array(
+ new DateTime( '@' . $first_info['start'] ),
+ new DateTime( '@' . ( $second_info['end'] - 50 ) ),
+ )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_both' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that it uses the block logs when the period fits one block exactly.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs_two_blocks_end() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $second_info = $block->get_block_info( current_time( 'timestamp' ) );
+ $first_info = $block->get_block_info( $second_info['start'] - 1 );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_both' )
+ , array(
+ new DateTime( '@' . ( $first_info['start'] + 50 ) ),
+ new DateTime( '@' . $second_info['end'] ),
+ )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_both' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that it uses the block logs when the period fits the blocks exactly.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs_only_one_block() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $info = $block->get_block_info( current_time( 'timestamp' ) );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_block_logs' )
+ , array(
+ new DateTime( '@' . $info['start'] ),
+ new DateTime( '@' . $info['end'] ),
+ )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_block_logs' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests that it uses the block logs when the period fits the blocks exactly.
+ *
+ * @since 1.0.0
+ */
+ public function test_should_use_block_logs_only_multiple_blocks() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $end_info = $block->get_block_info( current_time( 'timestamp' ) );
+ $start_info = $block->get_block_info(
+ $end_info['start'] - 2 * WEEK_IN_SECONDS
+ );
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_sql_for_block_logs' )
+ , array(
+ new DateTime( '@' . $start_info['start'] ),
+ new DateTime( '@' . $end_info['end'] ),
+ )
+ );
+
+ $stub->expects( $this->once() )
+ ->method( 'get_sql_for_block_logs' )
+ ->willReturn( 'test' );
+
+ $this->assertSame( 'test', $stub->get_sql() );
+ }
+
+ /**
+ * Tests getting the results.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_get_results(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+ }
+
+ /**
+ * Data provider for start and end dates to test getting results for.
+ *
+ * @since 1.0.0
+ *
+ * @return array[] The sets of start and end dates.
+ */
+ public function data_provider_start_end_dates() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $end_info = $block->get_block_info( current_time( 'timestamp' ) );
+ $start_info = $block->get_block_info(
+ $end_info['start'] - 2 * WEEK_IN_SECONDS
+ );
+
+ $now = new DateTime();
+
+ return array(
+ 'points_logs' => array( new DateTime( '-1 day' ), $now ),
+ 'both' => array( new DateTime( '-1 month' ), $now, 2 * WEEK_IN_SECONDS ),
+ 'both_start_block_exact' => array(
+ new DateTime( '@' . $start_info['start'] ),
+ new DateTime( '@' . ( $end_info['end'] - 50 ) ),
+ ),
+ 'both_end_block_exact' => array(
+ new DateTime( '@' . ( $start_info['start'] + 50 ) ),
+ new DateTime( '@' . $end_info['end'] ),
+ ),
+ 'one_block_exactly' => array(
+ new DateTime( '@' . $start_info['start'] ),
+ new DateTime( '@' . $start_info['end'] ),
+ ),
+ 'multiple_blocks_exactly' => array(
+ new DateTime( '@' . $start_info['start'] ),
+ new DateTime( '@' . $end_info['end'] ),
+ ),
+ );
+ }
+
+ /**
+ * Tests getting the results.
+ *
+ * @since 1.0.0
+ *
+ * @expectedIncorrectUsage WordPoints_Top_Users_In_Period_Points_Logs_Query::prepare_select
+ */
+ public function test_arg_fields() {
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 month' )
+ , null
+ , array( 'fields' => 'total' )
+ );
+
+ $this->assertSame( array(), $query->get() );
+ }
+
+ /**
+ * Tests the 'limit' arg.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_limit(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'limit' => 1 )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'start' arg.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_start(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'start' => 1, 'limit' => 1 )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'order' arg.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_order(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'order' => 'ASC' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[1]
+ );
+ }
+
+ /**
+ * Tests the 'order_by' arg.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_order_by(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'order_by' => 'user_id' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[1]
+ );
+ }
+
+ /**
+ * Tests the 'id*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_id(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ // A transaction that took place right at the start.
+ $log_id = $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place right at the end.
+ $log_id_2 = $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[1],
+ 'points' => 64,
+ 'date' => $end_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // 'id' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'id' => $log_id, 'id__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '66', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[1]
+ );
+
+ // 'id__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'id__in' => array( $log_id, $log_id_2 ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '64', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[0] )
+ , $results[1]
+ );
+
+ // 'id__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'id__not_in' => array( $log_id, $log_id_2 ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+ }
+
+ /**
+ * Tests the 'user_id*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_user_id(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+ $user_ids[2] = $this->factory->user->create();
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[2],
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // 'user_id' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'user_id' => $user_ids[2] )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[2] )
+ , $results[0]
+ );
+
+ // 'user_id__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'user_id' => $user_ids[2], 'user_id__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'user_id__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'user_id__in' => array( $user_ids[0], $user_ids[2] ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[2] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[1]
+ );
+
+ // 'user_id__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'user_id__not_in' => array( $user_ids[0], $user_ids[2] ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'points_type*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_points_type(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points_type' => 'other',
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points_type' => 'third',
+ 'points' => 64,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // 'points_type' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points_type' => 'points' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'points_type__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points_type' => 'points', 'points_type__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '96', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ // 'points_type__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points_type__in' => array( 'points', 'other' ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '37', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'points_type__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points_type__not_in' => array( 'points', 'other' ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '64', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'log_type*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_log_type(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'log_type' => 'other',
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'log_type' => 'third',
+ 'points' => 64,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // 'log_type' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'log_type' => 'test' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'log_type__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'log_type' => 'test', 'log_type__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '96', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ // 'log_type__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'log_type__in' => array( 'test', 'other' ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '37', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'log_type__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'log_type__not_in' => array( 'test', 'other' ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '64', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'text*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_text(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'text' => 'other',
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'text' => 'third',
+ 'points' => 64,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // 'text' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'text' => 'Log text%' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'text__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'text' => 'other', 'text__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '69', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'text__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'text__in' => array( 'third', 'other' ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '96', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ // 'text__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'text__not_in' => array( 'third', 'other' ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+ }
+
+ /**
+ * Tests the 'points*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_points(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ // 'points' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points' => 4 )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '4', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ // 'points__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points' => 4, 'points__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '1', 'user_id' => (string) $user_ids[0] )
+ , $results[1]
+ );
+
+ // 'points__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points__in' => array( 2, 4 ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '4', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'points__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'points__not_in' => array( 2, 4 ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '1', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'total*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_total(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+ $user_ids[2] = $this->factory->user->create();
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[2],
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // 'total' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'total' => 5 )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ // 'total__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'total' => 5, 'total__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[2] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'total__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'total__in' => array( 2, 5 ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'total__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'total__not_in' => array( 2, 5 ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[2] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'meta*' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_meta(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ 'meta' => array( 'test_1' => 'a' ),
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'meta_query' => array( array( 'key' => 'test_1' ) ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'date_query' args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_date(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $date = clone $start_date;
+
+ // Add 45 minutes to the start time.
+ $date->modify( '+45 minutes' );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points' => 32,
+ 'date' => $date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // Subtract an hour.
+ $date->modify( '-1 hour' );
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points' => 64,
+ 'date' => $date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array(
+ 'date_query' => array(
+ array( 'minute' => $date->format( 'i' ), 'compare' => '=' ),
+ ),
+ )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '32', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests the 'blog_id*' args.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ *
+ * @dataProvider data_provider_start_end_dates
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ */
+ public function test_arg_blog_id(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle = null
+ ) {
+
+ $user_ids = $this->create_logs( $start_date, $end_date, $to_middle );
+
+ $current_blog_id = get_current_blog_id();
+
+ $blog_id = $this->factory->blog->create();
+
+ switch_to_blog( $blog_id );
+
+ if ( ! is_wordpoints_network_active() ) {
+ wordpoints_add_points_type( array( 'name' => 'points' ) );
+ }
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points' => 32,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ restore_current_blog();
+
+ switch_to_blog( $this->factory->blog->create() );
+
+ if ( ! is_wordpoints_network_active() ) {
+ wordpoints_add_points_type( array( 'name' => 'points' ) );
+ }
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_ids[0],
+ 'points' => 64,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ restore_current_blog();
+
+ // 'blog_id' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'blog_id' => $current_blog_id )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '5', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'blog_id__compare' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'blog_id' => $current_blog_id, 'blog_id__compare' => '!=' )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '96', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ // 'blog_id__in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'blog_id__in' => array( $current_blog_id, $blog_id ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '37', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '2', 'user_id' => (string) $user_ids[1] )
+ , $results[1]
+ );
+
+ // 'blog_id__not_in' arg.
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ $start_date
+ , $end_date
+ , array( 'blog_id__not_in' => array( $current_blog_id, $blog_id ) )
+ );
+
+ $results = $query->get();
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSameProperties(
+ (object) array( 'total' => '64', 'user_id' => (string) $user_ids[0] )
+ , $results[0]
+ );
+ }
+
+ /**
+ * Tests that it returns an error if there was one while verifying the blocks.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_sql_for_block_logs_error() {
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'get_and_verify_blocks' )
+ , array( new DateTime( '-1 months' ) )
+ );
+
+ $error = new WP_Error();
+
+ $stub->method( 'get_and_verify_blocks' )
+ ->willReturn( $error );
+
+ $this->assertSame( $error, $stub->get() );
+ }
+
+ /**
+ * Tests that it returns an error if there were any draft blocks.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_and_verify_blocks_draft_blocks() {
+
+ global $wpdb;
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $info = $block->get_block_info(
+ current_time( 'timestamp' ) - WEEK_IN_SECONDS
+ );
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'block_type' => 'week_in_seconds',
+ 'start_date' => date( 'Y-m-d H:i:s', $info['start'] ),
+ 'end_date' => date( 'Y-m-d H:i:s', $info['end'] ),
+ 'query_signature' => self::$default_signature,
+ 'status' => 'draft',
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertWPError( $query->get() );
+ }
+
+ /**
+ * Tests that it only gets blocks of the correct type.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_blocks_other_type() {
+
+ global $wpdb;
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $info = $block->get_block_info(
+ current_time( 'timestamp' ) - WEEK_IN_SECONDS
+ );
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'block_type' => 'other',
+ 'start_date' => date( 'Y-m-d H:i:s', $info['start'] ),
+ 'end_date' => date( 'Y-m-d H:i:s', $info['end'] ),
+ 'query_signature' => self::$default_signature,
+ 'status' => 'draft',
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( array(), $query->get() );
+ }
+
+ /**
+ * Tests that it only gets blocks for the same query signature.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_blocks_other_signature() {
+
+ global $wpdb;
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $info = $block->get_block_info(
+ current_time( 'timestamp' ) - WEEK_IN_SECONDS
+ );
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'block_type' => 'week_in_seconds',
+ 'start_date' => date( 'Y-m-d H:i:s', $info['start'] ),
+ 'end_date' => date( 'Y-m-d H:i:s', $info['end'] ),
+ 'query_signature' => 'other',
+ 'status' => 'draft',
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 months' )
+ );
+
+ $this->assertSame( array(), $query->get() );
+ }
+
+ /**
+ * Tests that it only gets blocks for the period.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_blocks_outside_period() {
+
+ global $wpdb;
+
+ $start_date = new DateTime( '-1 months' );
+ $end_date = new DateTime();
+
+ $start = (int) $start_date->format( 'U' );
+ $end = (int) $end_date->format( 'U' );
+
+ // Before the period.
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'block_type' => 'week_in_seconds',
+ 'start_date' => date( 'Y-m-d H:i:s', $start - WEEK_IN_SECONDS ),
+ 'end_date' => date( 'Y-m-d H:i:s', $start - 1 ),
+ 'query_signature' => self::$default_signature,
+ 'status' => 'draft',
+ )
+ );
+
+ // After the period.
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'block_type' => 'week_in_seconds',
+ 'start_date' => date( 'Y-m-d H:i:s', $end + 1 ),
+ 'end_date' => date( 'Y-m-d H:i:s', $end + WEEK_IN_SECONDS ),
+ 'query_signature' => self::$default_signature,
+ 'status' => 'draft',
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+
+ $this->assertSame( array(), $query->get() );
+ }
+
+ /**
+ * Tests that it returns an error if there were any draft blocks.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_and_verify_blocks_failed_filling_block_logs() {
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'fill_block_logs' )
+ , array( new DateTime( '-1 months' ) )
+ );
+
+ $stub->method( 'fill_block_logs' )->willReturn( false );
+
+ $this->assertWPError( $stub->get() );
+ }
+
+ /**
+ * Tests that it creates the missing blocks correctly when all are missing.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_blocks
+ *
+ * @param int[][] $expected_blocks Start and end timestamps for expected blocks.
+ * @param int $period_start Timestamp of the start of the period.
+ * @param int $period_end Timestamp of the end of the period.
+ * @param int[][] $pre_filled Start and end timestamps for blocks to pre-
+ * fill before running the query.
+ */
+ public function test_check_for_missing_blocks(
+ $expected_blocks,
+ $period_start = null,
+ $period_end = null,
+ $pre_filled = array()
+ ) {
+
+ global $wpdb;
+
+ // Pre-fill any blocks requested to be pre-filled.
+ foreach ( $pre_filled as $block ) {
+
+ $wpdb->insert(
+ $wpdb->base_prefix . 'wordpoints_top_users_in_period_blocks'
+ , array(
+ 'block_type' => 'week_in_seconds',
+ 'start_date' => date( 'Y-m-d H:i:s', $block['start'] ),
+ 'end_date' => date( 'Y-m-d H:i:s', $block['end'] ),
+ 'query_signature' => self::$default_signature,
+ 'status' => 'filled',
+ )
+ );
+ }
+
+ // Construct the query.
+ $count = count( $expected_blocks );
+
+ if ( ! isset( $period_start ) ) {
+ $period_start = $expected_blocks[0]['start'];
+ }
+
+ if ( ! isset( $period_end ) ) {
+ $period_end = $expected_blocks[ $count - 1 ]['end'];
+ }
+
+ $start_date = new DateTime( '@' . $period_start );
+ $end_date = new DateTime( '@' . $period_end );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+
+ $this->assertSame( array(), $query->get() );
+
+ // Check that the expected blocks now exist in the database.
+ $blocks = $wpdb->get_results(
+ "
+ SELECT *
+ FROM {$wpdb->base_prefix}wordpoints_top_users_in_period_blocks
+ ORDER BY `start_date` ASC
+ "
+ );
+
+ $this->assertCount( $count, $blocks );
+
+ foreach ( $expected_blocks as $index => $block ) {
+
+ $this->assertSame(
+ date( 'Y-m-d H:i:s', $block['start'] )
+ , $blocks[ $index ]->start_date
+ );
+
+ $this->assertSame(
+ date( 'Y-m-d H:i:s', $block['end'] )
+ , $blocks[ $index ]->end_date
+ );
+ }
+ }
+
+ /**
+ * Data provider for sets of blocks.
+ *
+ * @since 1.0.0
+ *
+ * @return array[] Sets of blocks.
+ */
+ public function data_provider_blocks() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $first_info = $block->get_block_info(
+ current_time( 'timestamp' ) - MONTH_IN_SECONDS
+ );
+
+ $second_info = $block->get_block_info( $first_info['end'] + 1 );
+ $third_info = $block->get_block_info( $second_info['end'] + 1 );
+ $fourth_info = $block->get_block_info( $third_info['end'] + 1 );
+ $fifth_info = $block->get_block_info( $fourth_info['end'] + 1 );
+
+ return array(
+ 'all_one_exactly' => array( array( $first_info ) ),
+ 'all_one_fits_start' => array(
+ array( $first_info ),
+ $first_info['start'],
+ $first_info['end'] + HOUR_IN_SECONDS,
+ ),
+ 'all_one_fits_end' => array(
+ array( $first_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $first_info['end'],
+ ),
+ 'all_one' => array(
+ array( $first_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $first_info['end'] + HOUR_IN_SECONDS,
+ ),
+ 'all_multiple_exactly' => array(
+ array( $first_info, $second_info, $third_info ),
+ ),
+ 'all_multiple_fits_start' => array(
+ array( $first_info, $second_info, $third_info ),
+ $first_info['start'],
+ $third_info['end'] + HOUR_IN_SECONDS,
+ ),
+ 'all_multiple_fits_end' => array(
+ array( $first_info, $second_info, $third_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $third_info['end'],
+ ),
+ 'all_multiple' => array(
+ array( $first_info, $second_info, $third_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $third_info['end'] + HOUR_IN_SECONDS,
+ ),
+ 'start_multiple_exactly' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ null,
+ null,
+ array( $third_info, $fourth_info ),
+ ),
+ 'start_multiple_fits_start' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'],
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $third_info, $fourth_info ),
+ ),
+ 'start_multiple_fits_end' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'],
+ array( $third_info, $fourth_info ),
+ ),
+ 'start_multiple' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $third_info, $fourth_info ),
+ ),
+ 'end_multiple_exactly' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ null,
+ null,
+ array( $first_info, $second_info ),
+ ),
+ 'end_multiple_fits_start' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'],
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $first_info, $second_info ),
+ ),
+ 'end_multiple_fits_end' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'],
+ array( $first_info, $second_info ),
+ ),
+ 'end_multiple' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $first_info, $second_info ),
+ ),
+ 'middle_multiple_exactly' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ null,
+ null,
+ array( $first_info, $fourth_info ),
+ ),
+ 'middle_multiple_fits_start' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'],
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $first_info, $fourth_info ),
+ ),
+ 'middle_multiple_fits_end' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'],
+ array( $first_info, $fourth_info ),
+ ),
+ 'middle_multiple' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $first_info, $fourth_info ),
+ ),
+ 'both_ends_multiple_exactly' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ null,
+ null,
+ array( $second_info, $third_info ),
+ ),
+ 'both_ends_multiple_fits_start' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'],
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $second_info, $third_info ),
+ ),
+ 'both_ends_multiple_fits_end' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'],
+ array( $second_info, $third_info ),
+ ),
+ 'both_ends_multiple' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fourth_info['end'] + HOUR_IN_SECONDS,
+ array( $second_info, $third_info ),
+ ),
+ 'all_three_multiple_exactly' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info, $fifth_info ),
+ null,
+ null,
+ array( $second_info, $fourth_info ),
+ ),
+ 'all_three_multiple_fits_start' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info, $fifth_info ),
+ $first_info['start'],
+ $fifth_info['end'] + HOUR_IN_SECONDS,
+ array( $second_info, $fourth_info ),
+ ),
+ 'all_three_multiple_fits_end' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info, $fifth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fifth_info['end'],
+ array( $second_info, $fourth_info ),
+ ),
+ 'all_three_multiple' => array(
+ array( $first_info, $second_info, $third_info, $fourth_info, $fifth_info ),
+ $first_info['start'] - HOUR_IN_SECONDS,
+ $fifth_info['end'] + HOUR_IN_SECONDS,
+ array( $second_info, $fourth_info ),
+ ),
+ );
+ }
+
+ /**
+ * Tests that it returns an error if a draft block couldn't be saved (or filled).
+ *
+ * @since 1.0.0
+ */
+ public function test_fill_block_logs_error_saving_draft_block() {
+
+ $stub = $this->getMock(
+ 'WordPoints_Top_Users_In_Period_Query'
+ , array( 'save_draft_block' )
+ , array( new DateTime( '-1 month' ) )
+ );
+
+ $stub->method( 'save_draft_block' )->willReturn( false );
+
+ $this->assertWPError( $stub->get() );
+ }
+
+ /**
+ * Tests filling the block logs from the points logs.
+ *
+ * @since 1.0.0
+ */
+ public function test_fill_block_logs() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $start_info = $block->get_block_info( current_time( 'timestamp' ) );
+
+ $start_date = new DateTime( '@' . $start_info['start'] );
+ $end_date = new DateTime( '@' . $start_info['end'] );
+
+ $user_id_1 = $this->factory->user->create();
+ $user_id_2 = $this->factory->user->create();
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_1,
+ 'points' => 1,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place right at the end.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_2,
+ 'points' => 2,
+ 'date' => $end_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place in the middle.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_1,
+ 'points' => 3,
+ 'date' => date(
+ 'Y-m-d H:i:s'
+ , (int) $start_date->format( 'U' ) + HOUR_IN_SECONDS
+ ),
+ )
+ );
+
+ // A transaction that took place just before the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_1,
+ 'points' => 5,
+ 'date' => date(
+ 'Y-m-d H:i:s'
+ , (int) $start_date->format( 'U' ) - 1
+ ),
+ )
+ );
+
+ // A transaction that took place just after the end.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_2,
+ 'points' => 7,
+ 'date' => date(
+ 'Y-m-d H:i:s'
+ , (int) $end_date->format( 'U' ) + 1
+ ),
+ )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+ $query->get();
+
+ global $wpdb;
+
+ $results = $wpdb->get_results(
+ "
+ SELECT *
+ FROM {$wpdb->base_prefix}wordpoints_top_users_in_period_block_logs
+ ORDER BY `user_id` ASC
+ "
+ );
+
+ $this->assertCount( 2, $results );
+
+ $this->assertSame( '4', $results[0]->points );
+ $this->assertSame( (string) $user_id_1, $results[0]->user_id );
+
+ $this->assertSame( '2', $results[1]->points );
+ $this->assertSame( (string) $user_id_2, $results[1]->user_id );
+ }
+
+ /**
+ * Tests publishing a block.
+ *
+ * @since 1.0.0
+ */
+ public function test_publish_block() {
+
+ $block = new WordPoints_Top_Users_In_Period_Block_Type_Week_In_Seconds(
+ 'test'
+ );
+
+ $start_info = $block->get_block_info( current_time( 'timestamp' ) );
+
+ $start_date = new DateTime( '@' . $start_info['start'] );
+ $end_date = new DateTime( '@' . $start_info['end'] );
+
+ $query = new WordPoints_Top_Users_In_Period_Query( $start_date, $end_date );
+
+ $this->assertSame( array(), $query->get() );
+
+ global $wpdb;
+
+ $results = $wpdb->get_results(
+ "
+ SELECT *
+ FROM {$wpdb->base_prefix}wordpoints_top_users_in_period_blocks
+ "
+ );
+
+ $this->assertCount( 1, $results );
+
+ $this->assertSame( 'filled', $results[0]->status );
+ }
+
+ /**
+ * Create some points logs around a period.
+ *
+ * @since 1.0.0
+ *
+ * @param DateTime $start_date The start date.
+ * @param DateTime $end_date The end date.
+ * @param int $to_middle The distance to somewhere in the middle.
+ *
+ * @return int[] The IDs of the 2 users created.
+ */
+ protected function create_logs(
+ DateTime $start_date,
+ DateTime $end_date,
+ $to_middle
+ ) {
+
+ if ( ! isset( $to_middle ) ) {
+ $to_middle = HOUR_IN_SECONDS;
+ }
+
+ $user_id_1 = $this->factory->user->create();
+ $user_id_2 = $this->factory->user->create();
+
+ // A transaction that took place right at the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_1,
+ 'points' => 1,
+ 'date' => $start_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place right at the end.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_2,
+ 'points' => 2,
+ 'date' => $end_date->format( 'Y-m-d H:i:s' ),
+ )
+ );
+
+ // A transaction that took place in the middle.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_1,
+ 'points' => 4,
+ 'date' => date(
+ 'Y-m-d H:i:s'
+ , (int) $start_date->format( 'U' ) + $to_middle
+ ),
+ )
+ );
+
+ // A transaction that took place just before the start.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_1,
+ 'points' => 8,
+ 'date' => date(
+ 'Y-m-d H:i:s'
+ , (int) $start_date->format( 'U' ) - 1
+ ),
+ )
+ );
+
+ // A transaction that took place just after the end.
+ $this->factory->wordpoints->points_log->create(
+ array(
+ 'user_id' => $user_id_2,
+ 'points' => 16,
+ 'date' => date(
+ 'Y-m-d H:i:s'
+ , (int) $end_date->format( 'U' ) + 1
+ ),
+ )
+ );
+
+ return array( $user_id_1, $user_id_2 );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/query/cache/flusher.php b/tests/phpunit/tests/classes/query/cache/flusher.php
new file mode 100644
index 0000000..1c1b95e
--- /dev/null
+++ b/tests/phpunit/tests/classes/query/cache/flusher.php
@@ -0,0 +1,512 @@
+mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'test'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array(), 5 );
+
+ $this->assertNotEmpty( $index->get() );
+
+ $flusher = new WordPoints_Top_Users_In_Period_Query_Cache_Flusher();
+ $flusher->flush();
+
+ $this->assertFalse(
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Tests flushing the cache when the cache type isn't registered.
+ *
+ * @since 1.0.0
+ */
+ public function test_flush_cache_type_not_registered() {
+
+ $this->mock_apps();
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array(), 5 );
+
+ $this->assertNotEmpty( $index->get() );
+
+ $flusher = new WordPoints_Top_Users_In_Period_Query_Cache_Flusher();
+ $flusher->flush();
+
+ $this->assertSame(
+ $cache
+ , WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Tests flushing the cache on multisite also flushes the network cache.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_flush_multisite() {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'test'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index( true );
+
+ $index->add( 'test', array(), 5 );
+
+ $this->assertNotEmpty( $index->get() );
+
+ $flusher = new WordPoints_Top_Users_In_Period_Query_Cache_Flusher();
+ $flusher->flush();
+
+ $this->assertFalse(
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Tests flushing the cache when the query does not match the specified args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_non_matching_queries
+ *
+ * @param array $query_args The args for the query.
+ * @param array $args The args to flush against.
+ */
+ public function test_flush_not_matching_query( $query_args, $args ) {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'test'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', $query_args, 5 );
+
+ $this->assertNotEmpty( $index->get() );
+
+ $flusher = new WordPoints_Top_Users_In_Period_Query_Cache_Flusher( $args );
+ $flusher->flush();
+
+ $this->assertSame(
+ $cache
+ , WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Provides sets of query args and flusher args that do not match.
+ *
+ * @since 1.0.0
+ *
+ * @return array[][] The sets of args.
+ */
+ public function data_provider_non_matching_queries() {
+ return array(
+ 'points_type_different' => array(
+ array( 'points_type' => 'points' ),
+ array( 'points_type' => 'other' ),
+ ),
+ 'points_type_different_compare_equals' => array(
+ array(
+ 'points_type' => 'points',
+ 'points_type__compare' => '=',
+ ),
+ array( 'points_type' => 'other' ),
+ ),
+ 'points_type_different_in' => array(
+ array( 'points_type__in' => array( 'one', 'two', 'points' ) ),
+ array( 'points_type' => 'other' ),
+ ),
+ 'points_type_same_not_in' => array(
+ array( 'points_type__not_in' => array( 'one', 'two', 'points' ) ),
+ array( 'points_type' => 'points' ),
+ ),
+ 'log_type_different' => array(
+ array( 'log_type' => 'test' ),
+ array( 'log_type' => 'other' ),
+ ),
+ 'log_type_different_compare_equals' => array(
+ array(
+ 'log_type' => 'test',
+ 'log_type__compare' => '=',
+ ),
+ array( 'log_type' => 'other' ),
+ ),
+ 'log_type_different_in' => array(
+ array( 'log_type__in' => array( 'one', 'two', 'test' ) ),
+ array( 'log_type' => 'other' ),
+ ),
+ 'log_type_same_not_in' => array(
+ array( 'log_type__not_in' => array( 'one', 'two', 'test' ) ),
+ array( 'log_type' => 'test' ),
+ ),
+ 'user_id_different' => array(
+ array( 'user_id' => 1 ),
+ array( 'user_id' => 2 ),
+ ),
+ 'user_id_different_compare_equals' => array(
+ array(
+ 'user_id' => 1,
+ 'user_id__compare' => '=',
+ ),
+ array( 'user_id' => 2 ),
+ ),
+ 'user_id_different_in' => array(
+ array( 'user_id__in' => array( 3, 4, 1 ) ),
+ array( 'user_id' => 2 ),
+ ),
+ 'user_id_same_not_in' => array(
+ array( 'user_id__not_in' => array( 3, 4, 1 ) ),
+ array( 'user_id' => 1 ),
+ ),
+ 'blog_id_different' => array(
+ array( 'blog_id' => 1 ),
+ array( 'blog_id' => 2 ),
+ ),
+ 'blog_id_different_compare_equals' => array(
+ array(
+ 'blog_id' => 1,
+ 'blog_id__compare' => '=',
+ ),
+ array( 'blog_id' => 2 ),
+ ),
+ 'blog_id_different_in' => array(
+ array( 'blog_id__in' => array( 3, 4, 1 ) ),
+ array( 'blog_id' => 2 ),
+ ),
+ 'blog_id_same_not_in' => array(
+ array( 'blog_id__not_in' => array( 3, 4, 1 ) ),
+ array( 'blog_id' => 1 ),
+ ),
+ 'site_id_different' => array(
+ array( 'site_id' => 1 ),
+ array( 'site_id' => 2 ),
+ ),
+ 'site_id_different_compare_equals' => array(
+ array(
+ 'site_id' => 1,
+ 'site_id__compare' => '=',
+ ),
+ array( 'site_id' => 2 ),
+ ),
+ 'site_id_different_in' => array(
+ array( 'site_id__in' => array( 3, 4, 1 ) ),
+ array( 'site_id' => 2 ),
+ ),
+ 'site_id_same_not_in' => array(
+ array( 'site_id__not_in' => array( 3, 4, 1 ) ),
+ array( 'site_id' => 1 ),
+ ),
+ );
+ }
+
+ /**
+ * Tests flushing the cache when the query matches the specified args.
+ *
+ * @since 1.0.0
+ *
+ * @dataProvider data_provider_matching_queries
+ *
+ * @param array $query_args The args for the query.
+ * @param array $args The args to flush against.
+ */
+ public function test_flush_matching_query( $query_args, $args ) {
+
+ $this->mock_apps();
+
+ wordpoints_module( 'top_users_in_period' )
+ ->get_sub_app( 'query_caches' )
+ ->register(
+ 'test'
+ , 'WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache'
+ );
+
+ $cache = array( 'test' );
+
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value = $cache;
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', $query_args, 5 );
+
+ $this->assertNotEmpty( $index->get() );
+
+ $flusher = new WordPoints_Top_Users_In_Period_Query_Cache_Flusher( $args );
+ $flusher->flush();
+
+ $this->assertFalse(
+ WordPoints_Top_Users_In_Period_PHPUnit_Mock_Query_Cache::$value
+ );
+ }
+
+ /**
+ * Provides sets of query args and flusher args that match.
+ *
+ * @since 1.0.0
+ *
+ * @return array[][] The sets of args.
+ */
+ public function data_provider_matching_queries() {
+ return array(
+ 'points_type_not_specified' => array(
+ array(),
+ array( 'points_type' => 'points' ),
+ ),
+ 'points_type_same' => array(
+ array( 'points_type' => 'points' ),
+ array( 'points_type' => 'points' ),
+ ),
+ 'points_type_different_compare_not_equals' => array(
+ array(
+ 'points_type' => 'points',
+ 'points_type__compare' => '!=',
+ ),
+ array( 'points_type' => 'other' ),
+ ),
+ 'points_type_same_compare_not_equals' => array(
+ array(
+ 'points_type' => 'points',
+ 'points_type__compare' => '!=',
+ ),
+ array( 'points_type' => 'points' ),
+ ),
+ 'points_type_same_compare_equals' => array(
+ array(
+ 'points_type' => 'points',
+ 'points_type__compare' => '=',
+ ),
+ array( 'points_type' => 'points' ),
+ ),
+ 'points_type_same_in' => array(
+ array( 'points_type__in' => array( 'one', 'two', 'points' ) ),
+ array( 'points_type' => 'points' ),
+ ),
+ 'points_type_different_not_in' => array(
+ array( 'points_type__not_in' => array( 'one', 'two', 'points' ) ),
+ array( 'points_type' => 'other' ),
+ ),
+ 'log_type_not_specified' => array(
+ array(),
+ array( 'log_type' => 'test' ),
+ ),
+ 'log_type_same' => array(
+ array( 'log_type' => 'test' ),
+ array( 'log_type' => 'test' ),
+ ),
+ 'log_type_different_compare_not_equals' => array(
+ array(
+ 'log_type' => 'test',
+ 'log_type__compare' => '!=',
+ ),
+ array( 'log_type' => 'other' ),
+ ),
+ 'log_type_same_compare_not_equals' => array(
+ array(
+ 'log_type' => 'test',
+ 'log_type__compare' => '!=',
+ ),
+ array( 'log_type' => 'test' ),
+ ),
+ 'log_type_same_compare_equals' => array(
+ array(
+ 'log_type' => 'test',
+ 'log_type__compare' => '=',
+ ),
+ array( 'log_type' => 'test' ),
+ ),
+ 'log_type_same_in' => array(
+ array( 'log_type__in' => array( 'one', 'two', 'test' ) ),
+ array( 'log_type' => 'test' ),
+ ),
+ 'log_type_different_not_in' => array(
+ array( 'log_type__not_in' => array( 'one', 'two', 'test' ) ),
+ array( 'log_type' => 'other' ),
+ ),
+ 'user_id_not_specified' => array(
+ array(),
+ array( 'user_id' => 1 ),
+ ),
+ 'user_id_same' => array(
+ array( 'user_id' => 1 ),
+ array( 'user_id' => 1 ),
+ ),
+ 'user_id_different_compare_not_equals' => array(
+ array(
+ 'user_id' => 1,
+ 'user_id__compare' => '!=',
+ ),
+ array( 'user_id' => 2 ),
+ ),
+ 'user_id_same_compare_not_equals' => array(
+ array(
+ 'user_id' => 1,
+ 'user_id__compare' => '!=',
+ ),
+ array( 'user_id' => 1 ),
+ ),
+ 'user_id_same_compare_equals' => array(
+ array(
+ 'user_id' => 1,
+ 'user_id__compare' => '=',
+ ),
+ array( 'user_id' => 1 ),
+ ),
+ 'user_id_same_in' => array(
+ array( 'user_id__in' => array( 3, 4, 1 ) ),
+ array( 'user_id' => 1 ),
+ ),
+ 'user_id_different_not_in' => array(
+ array( 'user_id__not_in' => array( 3, 4, 1 ) ),
+ array( 'user_id' => 2 ),
+ ),
+ 'blog_id_not_specified' => array(
+ array(),
+ array( 'blog_id' => 1 ),
+ ),
+ 'blog_id_same' => array(
+ array( 'blog_id' => 1 ),
+ array( 'blog_id' => 1 ),
+ ),
+ 'blog_id_different_compare_not_equals' => array(
+ array(
+ 'blog_id' => 1,
+ 'blog_id__compare' => '!=',
+ ),
+ array( 'blog_id' => 2 ),
+ ),
+ 'blog_id_same_compare_not_equals' => array(
+ array(
+ 'blog_id' => 1,
+ 'blog_id__compare' => '!=',
+ ),
+ array( 'blog_id' => 1 ),
+ ),
+ 'blog_id_same_compare_equals' => array(
+ array(
+ 'blog_id' => 1,
+ 'blog_id__compare' => '=',
+ ),
+ array( 'blog_id' => 1 ),
+ ),
+ 'blog_id_same_in' => array(
+ array( 'blog_id__in' => array( 3, 4, 1 ) ),
+ array( 'blog_id' => 1 ),
+ ),
+ 'blog_id_different_not_in' => array(
+ array( 'blog_id__not_in' => array( 3, 4, 1 ) ),
+ array( 'blog_id' => 2 ),
+ ),
+ 'site_id_not_specified' => array(
+ array(),
+ array( 'site_id' => 1 ),
+ ),
+ 'site_id_same' => array(
+ array( 'site_id' => 1 ),
+ array( 'site_id' => 1 ),
+ ),
+ 'site_id_different_compare_not_equals' => array(
+ array(
+ 'site_id' => 1,
+ 'site_id__compare' => '!=',
+ ),
+ array( 'site_id' => 2 ),
+ ),
+ 'site_id_same_compare_not_equals' => array(
+ array(
+ 'site_id' => 1,
+ 'site_id__compare' => '!=',
+ ),
+ array( 'site_id' => 1 ),
+ ),
+ 'site_id_same_compare_equals' => array(
+ array(
+ 'site_id' => 1,
+ 'site_id__compare' => '=',
+ ),
+ array( 'site_id' => 1 ),
+ ),
+ 'site_id_same_in' => array(
+ array( 'site_id__in' => array( 3, 4, 1 ) ),
+ array( 'site_id' => 1 ),
+ ),
+ 'site_id_different_not_in' => array(
+ array( 'site_id__not_in' => array( 3, 4, 1 ) ),
+ array( 'site_id' => 2 ),
+ ),
+ );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/query/cache/index.php b/tests/phpunit/tests/classes/query/cache/index.php
new file mode 100644
index 0000000..b40ee4e
--- /dev/null
+++ b/tests/phpunit/tests/classes/query/cache/index.php
@@ -0,0 +1,206 @@
+add( 'test', array(), 5 );
+
+ $this->assertSame(
+ array(
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' => array(
+ 'args' => array(),
+ 'caches' => array( 'test' => array( 5 => true ) ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests getting the index when it is empty.
+ *
+ * @since 1.0.0
+ */
+ public function test_get_empty() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $this->assertSame( array(), $index->get() );
+ }
+
+ /**
+ * Tests that order of the query args doesn't affect the query signature.
+ *
+ * @since 1.0.0
+ */
+ public function test_query_arg_order() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array( 'a' => 1, 'b' => 2 ), 5 );
+ $index->add( 'test', array( 'b' => 2, 'a' => 1 ), 5 );
+
+ $this->assertSame(
+ array(
+ '43258cff783fe7036d8a43033f830adfc60ec037382473548ac742b888292777' => array(
+ 'args' => array( 'a' => 1, 'b' => 2 ),
+ 'caches' => array( 'test' => array( 5 => true ) ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests adding two queries with the same args but different timestamps.
+ *
+ * @since 1.0.0
+ */
+ public function test_add_different_timestamp() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array(), 5 );
+ $index->add( 'test', array(), 4 );
+
+ $this->assertSame(
+ array(
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' => array(
+ 'args' => array(),
+ 'caches' => array( 'test' => array( 5 => true, 4 => true ) ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests adding two queries with the same args but of different types.
+ *
+ * @since 1.0.0
+ */
+ public function test_add_different_type() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array(), 5 );
+ $index->add( 'other', array(), 5 );
+
+ $this->assertSame(
+ array(
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' => array(
+ 'args' => array(),
+ 'caches' => array(
+ 'test' => array( 5 => true ),
+ 'other' => array( 5 => true ),
+ ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests adding two queries with the same timestamps but different args.
+ *
+ * @since 1.0.0
+ */
+ public function test_add_different_args() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array(), 5 );
+ $index->add( 'test', array( 'a' => 1, 'b' => 2 ), 5 );
+
+ $this->assertSame(
+ array(
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' => array(
+ 'args' => array(),
+ 'caches' => array( 'test' => array( 5 => true ) ),
+ ),
+ '43258cff783fe7036d8a43033f830adfc60ec037382473548ac742b888292777' => array(
+ 'args' => array( 'a' => 1, 'b' => 2 ),
+ 'caches' => array( 'test' => array( 5 => true ) ),
+ ),
+ )
+ , $index->get()
+ );
+ }
+
+ /**
+ * Tests that network options are used when when network wide is true.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_network() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index( true );
+
+ $index->add( 'test', array(), 5 );
+
+ $this->assertSame(
+ array(
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' => array(
+ 'args' => array(),
+ 'caches' => array( 'test' => array( 5 => true ) ),
+ ),
+ )
+ , get_site_option(
+ 'wordpoints_top_users_in_period_query_cache_index'
+ )
+ );
+ }
+
+ /**
+ * Tests that regular options are used when when network wide is not true.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_network_not() {
+
+ $index = new WordPoints_Top_Users_In_Period_Query_Cache_Index();
+
+ $index->add( 'test', array(), 5 );
+
+ $this->assertSame(
+ array(
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945' => array(
+ 'args' => array(),
+ 'caches' => array( 'test' => array( 5 => true ) ),
+ ),
+ )
+ , get_option(
+ 'wordpoints_top_users_in_period_query_cache_index'
+ )
+ );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/query/cache/transients.php b/tests/phpunit/tests/classes/query/cache/transients.php
new file mode 100644
index 0000000..d42a27e
--- /dev/null
+++ b/tests/phpunit/tests/classes/query/cache/transients.php
@@ -0,0 +1,207 @@
+assertTrue( $cache->set( $value ) );
+
+ $this->assertSame( $value, $cache->get() );
+
+ $this->assertTrue( $cache->delete() );
+
+ $this->assertFalse( $cache->get() );
+ }
+
+ /**
+ * Tests that order of the query args doesn't affect the query signature.
+ *
+ * @since 1.0.0
+ */
+ public function test_query_arg_order() {
+
+ $cache = new WordPoints_Top_Users_In_Period_Query_Cache_Transients(
+ 'test'
+ , array( 'a' => 1, 'b' => 2 )
+ );
+
+ $value = array( 'test' );
+
+ $this->assertTrue( $cache->set( $value ) );
+
+ $this->assertSame( $value, $cache->get() );
+
+ $cache = new WordPoints_Top_Users_In_Period_Query_Cache_Transients(
+ 'test'
+ , array( 'b' => 2, 'a' => 1 )
+ );
+
+ $this->assertSame( $value, $cache->get() );
+ }
+
+ /**
+ * Tests that network transients are used when appropriate.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ *
+ * @dataProvider data_provider_network_queries
+ */
+ public function test_network_transients( $args, $signature ) {
+
+ $transient_name = "wordpoints_top_users_in_period_query_{$signature}";
+
+ $cache = new WordPoints_Top_Users_In_Period_Query_Cache_Transients(
+ 'test'
+ , $args
+ , true
+ );
+
+ $value = array( 'test' );
+
+ $this->assertTrue( $cache->set( $value ) );
+
+ $this->assertSame( $value, $cache->get() );
+ $this->assertSame( $value, get_site_transient( $transient_name ) );
+ $this->assertFalse( get_transient( $transient_name ) );
+
+ $cache->delete();
+
+ $this->assertFalse( get_site_transient( $transient_name ) );
+ }
+
+ /**
+ * Data provider for network queries.
+ *
+ * @since 1.0.0
+ *
+ * @return array Network queries.
+ */
+ public function data_provider_network_queries() {
+ return array(
+ 'no_blog_id' => array(
+ array(),
+ '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
+ ),
+ 'different_blog_id' => array(
+ array( 'blog_id' => 2 ),
+ '32359a140e2c18c16533860d2a29715eb0af87b4af25f25477420ed30bd3b36a',
+ ),
+ 'blog_id_not_=' => array(
+ array( 'blog_id' => 1, 'blog_id__compare' => '!=' ),
+ '17c9321bb65e3af0f8439ac01aabd5043ea0e6e986a80660b198e420c10202c7',
+ ),
+ );
+ }
+
+ /**
+ * Tests that network transients are used when appropriate.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ *
+ * @dataProvider data_provider_per_site_queries
+ */
+ public function test_non_network_transients( $args, $signature ) {
+
+ $transient_name = "wordpoints_top_users_in_period_query_{$signature}";
+
+ $cache = new WordPoints_Top_Users_In_Period_Query_Cache_Transients(
+ 'test'
+ , $args
+ );
+
+ $value = array( 'test' );
+
+ $this->assertTrue( $cache->set( $value ) );
+
+ $this->assertSame( $value, $cache->get() );
+ $this->assertSame( $value, get_transient( $transient_name ) );
+ $this->assertFalse( get_site_transient( $transient_name ) );
+
+ $cache->delete();
+
+ $this->assertFalse( get_transient( $transient_name ) );
+ }
+
+ /**
+ * Data provider for per-site queries.
+ *
+ * @since 1.0.0
+ *
+ * @return array Per-site queries.
+ */
+ public function data_provider_per_site_queries() {
+ return array(
+ 'blog_id' => array(
+ array( 'blog_id' => 1 ),
+ '9db104c00a85d8cd2216f898a44a7f45a67b94de60dfe56eba7bef5a77f2ef67',
+ ),
+ 'blog_id_=' => array(
+ array( 'blog_id' => 1, 'blog_id__compare' => '=' ),
+ '5afa76bba28da6831d7b824b55e3bf511269804d567d1770c1a0646ce229599b',
+ ),
+ );
+ }
+
+ /**
+ * Tests that network transients are used when appropriate.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress !multisite
+ *
+ * @dataProvider data_provider_network_queries
+ */
+ public function test_non_network_transients_not_multisite( $args, $signature ) {
+
+ $transient_name = "wordpoints_top_users_in_period_query_{$signature}";
+
+ $cache = new WordPoints_Top_Users_In_Period_Query_Cache_Transients(
+ 'test'
+ , $args
+ );
+
+ $value = array( 'test' );
+
+ $this->assertTrue( $cache->set( $value ) );
+
+ $this->assertSame( $value, $cache->get() );
+ $this->assertSame( $value, get_transient( $transient_name ) );
+ $this->assertFalse( get_site_transient( $transient_name ) );
+
+ $cache->delete();
+
+ $this->assertFalse( get_transient( $transient_name ) );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/shortcode/dynamic.php b/tests/phpunit/tests/classes/shortcode/dynamic.php
new file mode 100644
index 0000000..8e17cb3
--- /dev/null
+++ b/tests/phpunit/tests/classes/shortcode/dynamic.php
@@ -0,0 +1,600 @@
+factory->user->create();
+
+ wordpoints_update_maybe_network_option(
+ 'wordpoints_excluded_users'
+ , array( $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $xpath = $this->get_shortcode_xpath( array( 'points_type' => 'points' ) );
+
+ $this->assertSame( 1, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test the users setting.
+ *
+ * @since 1.0.0
+ */
+ public function test_users_setting() {
+
+ $this->factory->wordpoints->points_log->create_many( 4 );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'points_type' => 'points', 'users' => 2 )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid period length results in the default being used.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_length() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'length' => 'invalid' ) )
+ );
+
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'length' => '' ) )
+ );
+ }
+
+ /**
+ * Test that and invalid period units results in the default being used.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_units() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'units' => 'invalid' ) )
+ );
+
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'units' => '' ) )
+ );
+ }
+
+ /**
+ * Test that and invalid period relative_to results in the default being used.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_relative_to() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'relative_to' => 'invalid' ) )
+ );
+
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'relative_to' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_seconds() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-29 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'length' => 30, 'units' => 'seconds' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_minutes() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-5 minutes' );
+ $date->setTime( $date->format( 'H' ), $date->format( 'i' ), 1 );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'length' => 5, 'units' => 'minutes' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_hours() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), $date->format( 'i' ), 1 );
+ $date->modify( '-2 hours' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'length' => 2, 'units' => 'hours' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_days() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), 0, 1 );
+ $date->modify( '-2 days' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath( array( 'length' => 2 ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_weeks() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->modify( '-2 weeks' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'length' => 2, 'units' => 'weeks' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_months() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->setDate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'm' ) - 2
+ , $date->format( 'd' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'length' => 2, 'units' => 'months' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_seconds_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-29 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 30,
+ 'units' => 'seconds',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_minutes_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), $date->format( 'i' ), 1 );
+ $date->modify( '-4 minutes' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 5,
+ 'units' => 'minutes',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_hours_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), 0, 1 );
+ $date->modify( '-1 hours' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 2,
+ 'units' => 'hours',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_days_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-1 days' );
+ $date->setTime( 0, 0, 1 );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 2,
+ 'units' => 'days',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_weeks_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->setISODate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'W' ) - 1
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 2,
+ 'units' => 'weeks',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_weeks_calendar_starts_on_sunday() {
+
+ update_option( 'start_of_week', 0 );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+
+ $weeks = 2;
+
+ if ( '7' === $date->format( 'w' ) ) {
+ $weeks = 1;
+ }
+
+ $date->setISODate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'W' ) - $weeks
+ , 7
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 2,
+ 'units' => 'weeks',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_months_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->setDate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'm' ) - 1
+ , 0
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array(
+ 'length' => 2,
+ 'units' => 'months',
+ 'relative_to' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/shortcode/fixed.php b/tests/phpunit/tests/classes/shortcode/fixed.php
new file mode 100644
index 0000000..418a8d6
--- /dev/null
+++ b/tests/phpunit/tests/classes/shortcode/fixed.php
@@ -0,0 +1,261 @@
+factory->user->create();
+
+ wordpoints_update_maybe_network_option(
+ 'wordpoints_excluded_users'
+ , array( $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $xpath = $this->get_shortcode_xpath( array( 'points_type' => 'points' ) );
+
+ $this->assertSame( 1, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test the num_users setting.
+ *
+ * @since 1.0.0
+ */
+ public function test_users_setting() {
+
+ $this->factory->wordpoints->points_log->create_many( 4 );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'points_type' => 'points', 'users' => 2 )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid from date results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_from() {
+
+ $this->give_current_user_caps( 'manage_options' );
+
+ $this->assertWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'from' => 'invalid' ) )
+ );
+
+ // It should not error when the from date is empty.
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'from' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the from date.
+ *
+ * @since 1.0.0
+ */
+ public function test_from() {
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-19 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-20 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-21 00:00:00' )
+ );
+
+ $xpath = $this->get_shortcode_xpath( array( 'from' => '2017-04-20' ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid from time results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_from_time() {
+
+ $this->give_current_user_caps( 'manage_options' );
+
+ $this->assertWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'from_time' => 'invalid' ) )
+ );
+
+ $this->assertWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'from_time' => '2017-04-31' ) )
+ );
+ }
+
+ /**
+ * Tests the from time.
+ *
+ * @since 1.0.0
+ */
+ public function test_from_time() {
+
+ $date = current_time( 'Y-m-d' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 05:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 06:00:00' )
+ );
+
+ $xpath = $this->get_shortcode_xpath( array( 'from_time' => '05:00' ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid to date results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_to() {
+
+ $this->give_current_user_caps( 'manage_options' );
+
+ $this->assertWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'to' => 'invalid' ) )
+ );
+
+ // It should not error when the to date is empty.
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'to' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the to date.
+ *
+ * @since 1.0.0
+ */
+ public function test_to() {
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-19 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-20 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-21 00:00:00' )
+ );
+
+ $xpath = $this->get_shortcode_xpath(
+ array( 'from' => '2017-03-20', 'to' => '2017-04-20' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid to time results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_to_time() {
+
+ $this->give_current_user_caps( 'manage_options' );
+
+ $this->assertWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode,
+ array( 'to' => '2017-04-21', 'to_time' => 'invalid' )
+ )
+ );
+
+ // It should not error when the to date is empty.
+ $this->assertNotWordPointsShortcodeError(
+ $this->do_shortcode( $this->shortcode, array( 'to_time' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the to time.
+ *
+ * @since 1.0.0
+ */
+ public function test_to_time() {
+
+ $date = current_time( 'Y-m-d' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 23:59:59' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 05:59:59' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 06:59:59' )
+ );
+
+ $xpath = $this->get_shortcode_xpath( array( 'to_time' => '05:59:59' ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/widget/dynamic.php b/tests/phpunit/tests/classes/widget/dynamic.php
new file mode 100644
index 0000000..2fa7d8a
--- /dev/null
+++ b/tests/phpunit/tests/classes/widget/dynamic.php
@@ -0,0 +1,644 @@
+give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html( array( 'points_type' => 'invalid' ) )
+ );
+
+ // It should not error when the points type is empty.
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'points_type' => '' ) )
+ );
+ }
+
+ /**
+ * Test the default behaviour of the widget.
+ *
+ * @since 1.0.0
+ */
+ public function test_defaults() {
+
+ $this->factory->wordpoints->points_log->create_many( 4 );
+
+ $xpath = $this->get_widget_xpath( array( 'points_type' => 'points' ) );
+
+ $this->assertSame( 3, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests that the excluded users are excluded.
+ *
+ * @since 1.0.0
+ */
+ public function test_excludes_excluded_users() {
+
+ $user_id = $this->factory->user->create();
+
+ wordpoints_update_maybe_network_option(
+ 'wordpoints_excluded_users'
+ , array( $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $xpath = $this->get_widget_xpath( array( 'points_type' => 'points' ) );
+
+ $this->assertSame( 1, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test the num_users setting.
+ *
+ * @since 1.0.0
+ */
+ public function test_num_users_setting() {
+
+ $this->factory->wordpoints->points_log->create_many( 4 );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'points_type' => 'points', 'num_users' => 2 )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid period length results in the default being used.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_length() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'length' => 'invalid' ) )
+ );
+
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'length' => '' ) )
+ );
+ }
+
+ /**
+ * Test that and invalid period units results in the default being used.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_units() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'units' => 'invalid' ) )
+ );
+
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'units' => '' ) )
+ );
+ }
+
+ /**
+ * Test that and invalid period relative results in the default being used.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_relative() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'relative' => 'invalid' ) )
+ );
+
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'relative' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_seconds() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-29 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'length_in_units' => 30, 'units' => 'seconds' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_minutes() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-5 minutes' );
+ $date->setTime( $date->format( 'H' ), $date->format( 'i' ), 1 );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'length_in_units' => 5, 'units' => 'minutes' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_hours() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), $date->format( 'i' ), 1 );
+ $date->modify( '-2 hours' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'length_in_units' => 2, 'units' => 'hours' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_days() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), 0, 1 );
+ $date->modify( '-2 days' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath( array( 'length_in_units' => 2 ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_weeks() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->modify( '-2 weeks' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'length_in_units' => 2, 'units' => 'weeks' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_months() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->setDate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'm' ) - 2
+ , $date->format( 'd' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'length_in_units' => 2, 'units' => 'months' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_seconds_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-29 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 30,
+ 'units' => 'seconds',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_minutes_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), $date->format( 'i' ), 1 );
+ $date->modify( '-4 minutes' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 5,
+ 'units' => 'minutes',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_hours_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( $date->format( 'H' ), 0, 1 );
+ $date->modify( '-1 hours' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 2,
+ 'units' => 'hours',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_days_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->modify( '-1 days' );
+ $date->setTime( 0, 0, 1 );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 2,
+ 'units' => 'days',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_weeks_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->setISODate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'W' ) - 1
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 2,
+ 'units' => 'weeks',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_weeks_calendar_starts_on_sunday() {
+
+ update_option( 'start_of_week', 0 );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+
+ $weeks = 2;
+
+ if ( '7' === $date->format( 'w' ) ) {
+ $weeks = 1;
+ }
+
+ $date->setISODate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'W' ) - $weeks
+ , 7
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 2,
+ 'units' => 'weeks',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests the period length.
+ *
+ * @since 1.0.0
+ */
+ public function test_length_months_calendar() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $date = new DateTime( null, $timezone );
+ $date->setTime( 0, 0, 1 );
+ $date->setDate(
+ $date->format( 'Y' )
+ , (int) $date->format( 'm' ) - 1
+ , 0
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $date->modify( '-3 seconds' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date->format( 'Y-m-d H:i:s' ) )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array(
+ 'length_in_units' => 2,
+ 'units' => 'months',
+ 'relative' => 'calendar',
+ )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test the update() method.
+ *
+ * @since 1.0.0
+ */
+ public function test_update_method() {
+
+ /** @var WP_Widget $widget */
+ $widget = new $this->widget_class;
+
+ $sanitized = $widget->update(
+ array(
+ 'title' => 'Title
',
+ 'num_users' => '5dd',
+ 'points_type' => 'invalid',
+ )
+ , array()
+ );
+
+ $this->assertSame( 'Title', $sanitized['title'] );
+ $this->assertSame( 3, $sanitized['num_users'] );
+ $this->assertSame( 'points', $sanitized['points_type'] );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/classes/widget/fixed.php b/tests/phpunit/tests/classes/widget/fixed.php
new file mode 100644
index 0000000..46daed5
--- /dev/null
+++ b/tests/phpunit/tests/classes/widget/fixed.php
@@ -0,0 +1,310 @@
+give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html( array( 'points_type' => 'invalid' ) )
+ );
+
+ // It should not error when the points type is empty.
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'points_type' => '' ) )
+ );
+ }
+
+ /**
+ * Test the default behaviour of the widget.
+ *
+ * @since 1.0.0
+ */
+ public function test_defaults() {
+
+ $this->factory->wordpoints->points_log->create_many( 4 );
+
+ $xpath = $this->get_widget_xpath( array( 'points_type' => 'points' ) );
+
+ $this->assertSame( 3, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Tests that the excluded users are excluded.
+ *
+ * @since 1.0.0
+ */
+ public function test_excludes_excluded_users() {
+
+ $user_id = $this->factory->user->create();
+
+ wordpoints_update_maybe_network_option(
+ 'wordpoints_excluded_users'
+ , array( $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id )
+ );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $xpath = $this->get_widget_xpath( array( 'points_type' => 'points' ) );
+
+ $this->assertSame( 1, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test the num_users setting.
+ *
+ * @since 1.0.0
+ */
+ public function test_num_users_setting() {
+
+ $this->factory->wordpoints->points_log->create_many( 4 );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'points_type' => 'points', 'num_users' => 2 )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid from date results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_from() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html( array( 'from' => 'invalid' ) )
+ );
+
+ // It should not error when the from date is empty.
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'from' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the from date.
+ *
+ * @since 1.0.0
+ */
+ public function test_from() {
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-19 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-20 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-21 00:00:00' )
+ );
+
+ $xpath = $this->get_widget_xpath( array( 'from' => '2017-04-20' ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid from time results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_from_time() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html( array( 'from_time' => 'invalid' ) )
+ );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html( array( 'from_time' => '2017-04-31' ) )
+ );
+
+ // It should not error when the from date is empty.
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'from_time' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the from time.
+ *
+ * @since 1.0.0
+ */
+ public function test_from_time() {
+
+ $date = current_time( 'Y-m-d' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 05:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 06:00:00' )
+ );
+
+ $xpath = $this->get_widget_xpath( array( 'from_time' => '05:00' ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid to date results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_to() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html( array( 'to' => 'invalid' ) )
+ );
+
+ // It should not error when the to date is empty.
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'to' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the to date.
+ *
+ * @since 1.0.0
+ */
+ public function test_to() {
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-19 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-20 00:00:00' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => '2017-04-21 00:00:00' )
+ );
+
+ $xpath = $this->get_widget_xpath(
+ array( 'from' => '2017-03-20', 'to' => '2017-04-20' )
+ );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test that and invalid to time results in an error.
+ *
+ * @since 1.0.0
+ */
+ public function test_invalid_to_time() {
+
+ $this->give_current_user_caps( 'edit_theme_options' );
+
+ $this->assertWordPointsWidgetError(
+ $this->get_widget_html(
+ array( 'to' => '2017-04-21', 'to_time' => 'invalid' )
+ )
+ );
+
+ // It should not error when the to date is empty.
+ $this->assertNotWordPointsWidgetError(
+ $this->get_widget_html( array( 'to_time' => '' ) )
+ );
+ }
+
+ /**
+ * Tests the to time.
+ *
+ * @since 1.0.0
+ */
+ public function test_to_time() {
+
+ $date = current_time( 'Y-m-d' );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 23:59:59' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 05:59:59' )
+ );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'date' => $date . ' 06:59:59' )
+ );
+
+ $xpath = $this->get_widget_xpath( array( 'to_time' => '05:59:59' ) );
+
+ $this->assertSame( 2, $xpath->query( '//tbody/tr' )->length );
+ }
+
+ /**
+ * Test the update() method.
+ *
+ * @since 1.0.0
+ */
+ public function test_update_method() {
+
+ /** @var WP_Widget $widget */
+ $widget = new $this->widget_class;
+
+ $sanitized = $widget->update(
+ array(
+ 'title' => 'Title
',
+ 'num_users' => '5dd',
+ 'points_type' => 'invalid',
+ )
+ , array()
+ );
+
+ $this->assertSame( 'Title', $sanitized['title'] );
+ $this->assertSame( 3, $sanitized['num_users'] );
+ $this->assertSame( 'points', $sanitized['points_type'] );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/functions/apps.php b/tests/phpunit/tests/functions/apps.php
new file mode 100644
index 0000000..4cd19cc
--- /dev/null
+++ b/tests/phpunit/tests/functions/apps.php
@@ -0,0 +1,96 @@
+mock_apps();
+
+ $app = new WordPoints_App( 'test' );
+
+ wordpoints_top_users_in_period_modules_app_init( $app );
+
+ $this->assertTrue(
+ $app->sub_apps()->is_registered( 'top_users_in_period' )
+ );
+ }
+
+ /**
+ * Tests the module's apps init function.
+ *
+ * @since 1.0.0
+ *
+ * @covers ::wordpoints_top_users_in_period_apps_init
+ */
+ public function test_apps_init() {
+
+ $this->mock_apps();
+
+ $app = new WordPoints_App( 'test' );
+
+ wordpoints_top_users_in_period_apps_init( $app );
+
+ $sub_apps = $app->sub_apps();
+
+ $this->assertTrue( $sub_apps->is_registered( 'block_types' ) );
+ $this->assertTrue( $sub_apps->is_registered( 'query_caches' ) );
+ }
+
+ /**
+ * Tests the block types registry init function.
+ *
+ * @since 1.0.0
+ *
+ * @covers ::wordpoints_top_users_in_period_block_types_init
+ */
+ public function test_block_types_init() {
+
+ $this->mock_apps();
+
+ $registry = new WordPoints_Class_Registry();
+
+ wordpoints_top_users_in_period_block_types_init( $registry );
+
+ $this->assertTrue( $registry->is_registered( 'week_in_seconds' ) );
+ }
+
+ /**
+ * Tests the query caches registry init function.
+ *
+ * @since 1.0.0
+ *
+ * @covers ::wordpoints_top_users_in_period_query_caches_init
+ */
+ public function test_query_caches_init() {
+
+ $this->mock_apps();
+
+ $registry = new WordPoints_Class_Registry();
+
+ wordpoints_top_users_in_period_query_caches_init( $registry );
+
+ $this->assertTrue( $registry->is_registered( 'transients' ) );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/functions/get-site-timezone.php b/tests/phpunit/tests/functions/get-site-timezone.php
new file mode 100644
index 0000000..13464d7
--- /dev/null
+++ b/tests/phpunit/tests/functions/get-site-timezone.php
@@ -0,0 +1,130 @@
+assertInstanceOf( 'DateTimeZone', $timezone );
+ $this->assertSame( 'America/New_York', $timezone->getName() );
+ }
+
+ /**
+ * Tests getting the timezone when the 'timezone_string' option is invalid.
+ *
+ * @since 1.0.0
+ */
+ public function test_timezone_string_invalid() {
+
+ update_option( 'timezone_string', 'invalid' );
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $this->assertInstanceOf( 'DateTimeZone', $timezone );
+ $this->assertSame( 'UTC', $timezone->getName() );
+ }
+
+ /**
+ * Tests getting the timezone when the 'gmt_offset' option is negative.
+ *
+ * @since 1.0.0
+ */
+ public function test_using_offset_negative() {
+
+ delete_option( 'timezone_string' );
+ update_option( 'gmt_offset', -5 );
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $this->assertInstanceOf( 'DateTimeZone', $timezone );
+
+ if ( version_compare( PHP_VERSION, '5.5', '>=' ) ) {
+ $this->assertSame( '-05:00', $timezone->getName() );
+ } else {
+ $this->assertSame( 'UTC', $timezone->getName() );
+ }
+ }
+
+ /**
+ * Tests getting the timezone when the 'gmt_offset' option is positive.
+ *
+ * @since 1.0.0
+ */
+ public function test_using_offset_positive() {
+
+ delete_option( 'timezone_string' );
+ update_option( 'gmt_offset', 7 );
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $this->assertInstanceOf( 'DateTimeZone', $timezone );
+
+ if ( version_compare( PHP_VERSION, '5.5', '>=' ) ) {
+ $this->assertSame( '+07:00', $timezone->getName() );
+ } else {
+ $this->assertSame( 'UTC', $timezone->getName() );
+ }
+ }
+
+ /**
+ * Tests getting the timezone when the 'gmt_offset' option is not an integer.
+ *
+ * @since 1.0.0
+ */
+ public function test_using_offset_decimal() {
+
+ delete_option( 'timezone_string' );
+ update_option( 'gmt_offset', 7.25 );
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $this->assertInstanceOf( 'DateTimeZone', $timezone );
+
+ if ( version_compare( PHP_VERSION, '5.5', '>=' ) ) {
+ $this->assertSame( '+07:15', $timezone->getName() );
+ } else {
+ $this->assertSame( 'UTC', $timezone->getName() );
+ }
+ }
+
+ /**
+ * Tests getting the timezone when the 'gmt_offset' option is zero.
+ *
+ * @since 1.0.0
+ */
+ public function test_using_offset_0() {
+
+ delete_option( 'timezone_string' );
+ update_option( 'gmt_offset', 0 );
+
+ $timezone = wordpoints_top_users_in_period_get_site_timezone();
+
+ $this->assertInstanceOf( 'DateTimeZone', $timezone );
+ $this->assertSame( 'UTC', $timezone->getName() );
+ }
+}
+
+// EOF
diff --git a/tests/phpunit/tests/functions/query-cached-flush-for-log.php b/tests/phpunit/tests/functions/query-cached-flush-for-log.php
new file mode 100644
index 0000000..21a9a6e
--- /dev/null
+++ b/tests/phpunit/tests/functions/query-cached-flush-for-log.php
@@ -0,0 +1,199 @@
+factory->wordpoints->points_log->create();
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 day' )
+ );
+
+ $this->listen_for_filter( 'query', array( $this, 'is_points_logs_query' ) );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query->get();
+
+ $this->assertSame( 2, $this->filter_was_called( 'query' ) );
+ }
+
+ /**
+ * Tests that it flushes the cache based on the points type of a new log.
+ *
+ * @since 1.0.0
+ */
+ public function test_flushes_cache_based_on_log_points_type() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 day' )
+ , null
+ , array( 'points_type' => 'points' )
+ );
+
+ $this->listen_for_filter( 'query', array( $this, 'is_points_logs_query' ) );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'points_type' => 'test' )
+ );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query->get();
+
+ $this->assertSame( 2, $this->filter_was_called( 'query' ) );
+ }
+
+ /**
+ * Tests that it flushes the cache based on the log type of a new log.
+ *
+ * @since 1.0.0
+ */
+ public function test_flushes_cache_based_on_log_type() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 day' )
+ , null
+ , array( 'log_type' => 'test' )
+ );
+
+ $this->listen_for_filter( 'query', array( $this, 'is_points_logs_query' ) );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'log_type' => 'other' )
+ );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query->get();
+
+ $this->assertSame( 2, $this->filter_was_called( 'query' ) );
+ }
+
+ /**
+ * Tests that it flushes the cache based on the user ID for a new log.
+ *
+ * @since 1.0.0
+ */
+ public function test_flushes_cache_based_on_log_user_id() {
+
+ $user_id = $this->factory->user->create();
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id )
+ );
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 day' )
+ , null
+ , array( 'user_id' => $user_id )
+ );
+
+ $this->listen_for_filter( 'query', array( $this, 'is_points_logs_query' ) );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'user_id' => $user_id )
+ );
+
+ $query->get();
+
+ $this->assertSame( 2, $this->filter_was_called( 'query' ) );
+ }
+
+ /**
+ * Tests that it flushes the cache based on the blog ID for a new log.
+ *
+ * @since 1.0.0
+ *
+ * @requires WordPress multisite
+ */
+ public function test_flushes_cache_based_on_log_blog_id() {
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query = new WordPoints_Top_Users_In_Period_Query(
+ new DateTime( '-1 day' )
+ );
+
+ $this->listen_for_filter( 'query', array( $this, 'is_points_logs_query' ) );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create(
+ array( 'blog_id' => $this->factory->blog->create() )
+ );
+
+ $query->get();
+
+ $this->assertSame( 1, $this->filter_was_called( 'query' ) );
+
+ $this->factory->wordpoints->points_log->create();
+
+ $query->get();
+
+ $this->assertSame( 2, $this->filter_was_called( 'query' ) );
+ }
+}
+
+// EOF
diff --git a/wp-l10n-validator.json b/wp-l10n-validator.json
new file mode 100644
index 0000000..5109f45
--- /dev/null
+++ b/wp-l10n-validator.json
@@ -0,0 +1,23 @@
+{
+ "textdomain": "wordpoints-top-users-in-period",
+ "basedir": "./src",
+ "bootstrap": "dev-lib/l10n-validator/bootstrap.php",
+ "cache": "./.wp-l10n-validator-cache.json",
+ "ignores-cache": "./.wp-l10n-validator-ignores-cache.json",
+ "ignores-tolerance": 25,
+ "ignored-functions": {
+ "WordPoints_Top_Users_In_Period_Table::__construct": [2],
+ "WordPoints_Top_Users_In_Period_Table::display_message": [2],
+ "WordPoints_Points_Widget_Top_Users::get_field_name": true,
+ "$block_types->register": true,
+ "$query_caches->register": true
+ },
+ "ignored-strings": [
+ "@",
+ ":00",
+ ":59",
+ "00:00",
+ "23:59",
+ "Y-m-d H:i:s"
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..2dd396c
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,3871 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+"@martin-pettersson/wp-get-file-data@^0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@martin-pettersson/wp-get-file-data/-/wp-get-file-data-0.1.0.tgz#5a26adb750227cf628ecaa728739a19273e14673"
+
+abbrev@1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
+
+acorn@^4.0.3:
+ version "4.0.11"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
+
+ajv@^4.9.1:
+ version "4.11.5"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.5.tgz#b6ee74657b993a01dce44b7944d56f485828d5bd"
+ dependencies:
+ co "^4.6.0"
+ json-stable-stringify "^1.0.1"
+
+align-text@^0.1.1, align-text@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+ dependencies:
+ kind-of "^3.0.2"
+ longest "^1.0.1"
+ repeat-string "^1.5.2"
+
+amdefine@>=0.0.4:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+anymatch@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
+ dependencies:
+ arrify "^1.0.0"
+ micromatch "^2.1.5"
+
+aproba@^1.0.3:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.1.tgz#95d3600f07710aa0e9298c726ad5ecf2eacbabab"
+
+archive-type@^3.0.0, archive-type@^3.0.1:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-3.2.0.tgz#9cd9c006957ebe95fadad5bd6098942a813737f6"
+ dependencies:
+ file-type "^3.1.0"
+
+are-we-there-yet@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3"
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^2.0.0 || ^1.1.13"
+
+argparse@^1.0.2, argparse@^1.0.7:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
+ dependencies:
+ sprintf-js "~1.0.2"
+
+arr-diff@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
+ dependencies:
+ arr-flatten "^1.0.1"
+
+arr-flatten@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b"
+
+array-differ@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
+
+array-filter@~0.0.0:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
+
+array-find-index@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
+
+array-map@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
+
+array-reduce@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
+
+array-uniq@^1.0.0, array-uniq@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
+array-unique@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+
+arrify@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+
+asn1.js@^4.0.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40"
+ dependencies:
+ bn.js "^4.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+asn1@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
+
+assert-plus@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
+
+assert-plus@^1.0.0, assert-plus@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
+assert@^1.4.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
+ dependencies:
+ util "0.10.3"
+
+astw@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/astw/-/astw-2.2.0.tgz#7bd41784d32493987aeb239b6b4e1c57a873b917"
+ dependencies:
+ acorn "^4.0.3"
+
+async-each-series@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/async-each-series/-/async-each-series-1.1.0.tgz#f42fd8155d38f21a5b8ea07c28e063ed1700b138"
+
+async-each@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
+
+async-foreach@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+
+async@^1.5.0, async@^1.5.2, async@~1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+
+autoprefixer@^6.6.1:
+ version "6.7.7"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
+ dependencies:
+ browserslist "^1.7.6"
+ caniuse-db "^1.0.30000634"
+ normalize-range "^0.1.2"
+ num2fraction "^1.2.2"
+ postcss "^5.2.16"
+ postcss-value-parser "^3.2.3"
+
+aws-sign2@~0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+
+aws4@^1.2.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+
+balanced-match@^0.4.1:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
+
+base64-js@^1.0.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
+ dependencies:
+ tweetnacl "^0.14.3"
+
+beeper@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
+
+bin-build@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/bin-build/-/bin-build-2.2.0.tgz#11f8dd61f70ffcfa2bdcaa5b46f5e8fedd4221cc"
+ dependencies:
+ archive-type "^3.0.1"
+ decompress "^3.0.0"
+ download "^4.1.2"
+ exec-series "^1.0.0"
+ rimraf "^2.2.6"
+ tempfile "^1.0.0"
+ url-regex "^3.0.0"
+
+bin-check@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/bin-check/-/bin-check-2.0.0.tgz#86f8e6f4253893df60dc316957f5af02acb05930"
+ dependencies:
+ executable "^1.0.0"
+
+bin-version-check@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-2.1.0.tgz#e4e5df290b9069f7d111324031efc13fdd11a5b0"
+ dependencies:
+ bin-version "^1.0.0"
+ minimist "^1.1.0"
+ semver "^4.0.3"
+ semver-truncate "^1.0.0"
+
+bin-version@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-1.0.4.tgz#9eb498ee6fd76f7ab9a7c160436f89579435d78e"
+ dependencies:
+ find-versions "^1.0.0"
+
+bin-wrapper@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/bin-wrapper/-/bin-wrapper-3.0.2.tgz#67d3306262e4b1a5f2f88ee23464f6a655677aeb"
+ dependencies:
+ bin-check "^2.0.0"
+ bin-version-check "^2.1.0"
+ download "^4.0.0"
+ each-async "^1.1.1"
+ lazy-req "^1.0.0"
+ os-filter-obj "^1.0.0"
+
+binary-extensions@^1.0.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774"
+
+bl@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.0.tgz#1397e7ec42c5f5dc387470c500e34a9f6be9ea98"
+ dependencies:
+ readable-stream "^2.0.5"
+
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ dependencies:
+ inherits "~2.0.0"
+
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
+ version "4.11.6"
+ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
+
+body-parser@~1.14.0:
+ version "1.14.2"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.14.2.tgz#1015cb1fe2c443858259581db53332f8d0cf50f9"
+ dependencies:
+ bytes "2.2.0"
+ content-type "~1.0.1"
+ debug "~2.2.0"
+ depd "~1.1.0"
+ http-errors "~1.3.1"
+ iconv-lite "0.4.13"
+ on-finished "~2.3.0"
+ qs "5.2.0"
+ raw-body "~2.1.5"
+ type-is "~1.6.10"
+
+boom@2.x.x:
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
+ dependencies:
+ hoek "2.x.x"
+
+brace-expansion@^1.0.0:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9"
+ dependencies:
+ balanced-match "^0.4.1"
+ concat-map "0.0.1"
+
+braces@^1.8.2:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
+ dependencies:
+ expand-range "^1.8.1"
+ preserve "^0.2.0"
+ repeat-element "^1.1.2"
+
+brorand@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
+
+browser-pack@^6.0.1:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.2.tgz#f86cd6cef4f5300c8e63e07a4d512f65fbff4531"
+ dependencies:
+ combine-source-map "~0.7.1"
+ defined "^1.0.0"
+ JSONStream "^1.0.3"
+ through2 "^2.0.0"
+ umd "^3.0.0"
+
+browser-resolve@^1.11.0, browser-resolve@^1.7.0:
+ version "1.11.2"
+ resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
+ dependencies:
+ resolve "1.1.7"
+
+browserify-aes@^1.0.0, browserify-aes@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
+ dependencies:
+ buffer-xor "^1.0.2"
+ cipher-base "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.0"
+ inherits "^2.0.1"
+
+browserify-cipher@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
+ dependencies:
+ browserify-aes "^1.0.4"
+ browserify-des "^1.0.0"
+ evp_bytestokey "^1.0.0"
+
+browserify-des@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
+ dependencies:
+ cipher-base "^1.0.1"
+ des.js "^1.0.0"
+ inherits "^2.0.1"
+
+browserify-rsa@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
+ dependencies:
+ bn.js "^4.1.0"
+ randombytes "^2.0.1"
+
+browserify-sign@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.0.tgz#10773910c3c206d5420a46aad8694f820b85968f"
+ dependencies:
+ bn.js "^4.1.1"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.2"
+ elliptic "^6.0.0"
+ inherits "^2.0.1"
+ parse-asn1 "^5.0.0"
+
+browserify-zlib@^0.1.4, browserify-zlib@~0.1.2:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
+ dependencies:
+ pako "~0.2.0"
+
+browserify@^13.0.0:
+ version "13.3.0"
+ resolved "https://registry.yarnpkg.com/browserify/-/browserify-13.3.0.tgz#b5a9c9020243f0c70e4675bec8223bc627e415ce"
+ dependencies:
+ assert "^1.4.0"
+ browser-pack "^6.0.1"
+ browser-resolve "^1.11.0"
+ browserify-zlib "~0.1.2"
+ buffer "^4.1.0"
+ cached-path-relative "^1.0.0"
+ concat-stream "~1.5.1"
+ console-browserify "^1.1.0"
+ constants-browserify "~1.0.0"
+ crypto-browserify "^3.0.0"
+ defined "^1.0.0"
+ deps-sort "^2.0.0"
+ domain-browser "~1.1.0"
+ duplexer2 "~0.1.2"
+ events "~1.1.0"
+ glob "^7.1.0"
+ has "^1.0.0"
+ htmlescape "^1.1.0"
+ https-browserify "~0.0.0"
+ inherits "~2.0.1"
+ insert-module-globals "^7.0.0"
+ JSONStream "^1.0.3"
+ labeled-stream-splicer "^2.0.0"
+ module-deps "^4.0.8"
+ os-browserify "~0.1.1"
+ parents "^1.0.1"
+ path-browserify "~0.0.0"
+ process "~0.11.0"
+ punycode "^1.3.2"
+ querystring-es3 "~0.2.0"
+ read-only-stream "^2.0.0"
+ readable-stream "^2.0.2"
+ resolve "^1.1.4"
+ shasum "^1.0.0"
+ shell-quote "^1.6.1"
+ stream-browserify "^2.0.0"
+ stream-http "^2.0.0"
+ string_decoder "~0.10.0"
+ subarg "^1.0.0"
+ syntax-error "^1.1.1"
+ through2 "^2.0.0"
+ timers-browserify "^1.0.1"
+ tty-browserify "~0.0.0"
+ url "~0.11.0"
+ util "~0.10.1"
+ vm-browserify "~0.0.1"
+ xtend "^4.0.0"
+
+browserify@^14.0.0:
+ version "14.1.0"
+ resolved "https://registry.yarnpkg.com/browserify/-/browserify-14.1.0.tgz#0508cc1e7bf4c152312c2fa523e676c0b0b92311"
+ dependencies:
+ assert "^1.4.0"
+ browser-pack "^6.0.1"
+ browser-resolve "^1.11.0"
+ browserify-zlib "~0.1.2"
+ buffer "^5.0.2"
+ cached-path-relative "^1.0.0"
+ concat-stream "~1.5.1"
+ console-browserify "^1.1.0"
+ constants-browserify "~1.0.0"
+ crypto-browserify "^3.0.0"
+ defined "^1.0.0"
+ deps-sort "^2.0.0"
+ domain-browser "~1.1.0"
+ duplexer2 "~0.1.2"
+ events "~1.1.0"
+ glob "^7.1.0"
+ has "^1.0.0"
+ htmlescape "^1.1.0"
+ https-browserify "~0.0.0"
+ inherits "~2.0.1"
+ insert-module-globals "^7.0.0"
+ JSONStream "^1.0.3"
+ labeled-stream-splicer "^2.0.0"
+ module-deps "^4.0.8"
+ os-browserify "~0.1.1"
+ parents "^1.0.1"
+ path-browserify "~0.0.0"
+ process "~0.11.0"
+ punycode "^1.3.2"
+ querystring-es3 "~0.2.0"
+ read-only-stream "^2.0.0"
+ readable-stream "^2.0.2"
+ resolve "^1.1.4"
+ shasum "^1.0.0"
+ shell-quote "^1.6.1"
+ stream-browserify "^2.0.0"
+ stream-http "^2.0.0"
+ string_decoder "~0.10.0"
+ subarg "^1.0.0"
+ syntax-error "^1.1.1"
+ through2 "^2.0.0"
+ timers-browserify "^1.0.1"
+ tty-browserify "~0.0.0"
+ url "~0.11.0"
+ util "~0.10.1"
+ vm-browserify "~0.0.1"
+ xtend "^4.0.0"
+
+browserslist@^1.7.6:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
+ dependencies:
+ caniuse-db "^1.0.30000639"
+ electron-to-chromium "^1.2.7"
+
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+
+buffer-shims@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
+
+buffer-to-vinyl@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz#00f15faee3ab7a1dda2cde6d9121bffdd07b2262"
+ dependencies:
+ file-type "^3.1.0"
+ readable-stream "^2.0.2"
+ uuid "^2.0.1"
+ vinyl "^1.0.0"
+
+buffer-xor@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
+
+buffer@^4.1.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+ isarray "^1.0.0"
+
+buffer@^5.0.2:
+ version "5.0.5"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.5.tgz#35c9393244a90aff83581063d16f0882cecc9418"
+ dependencies:
+ base64-js "^1.0.2"
+ ieee754 "^1.1.4"
+
+builtin-modules@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
+builtin-status-codes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+
+bytes@2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.2.0.tgz#fd35464a403f6f9117c2de3609ecff9cae000588"
+
+bytes@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
+
+cached-path-relative@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
+
+camelcase-keys@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+ dependencies:
+ camelcase "^2.0.0"
+ map-obj "^1.0.0"
+
+camelcase@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
+camelcase@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
+
+camelcase@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+
+caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
+ version "1.0.30000640"
+ resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000640.tgz#7b7fd3cf13c0d9d41f8754b577b202113e2be7ca"
+
+capture-stack-trace@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
+
+caseless@~0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+
+caw@^1.0.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/caw/-/caw-1.2.0.tgz#ffb226fe7efc547288dc62ee3e97073c212d1034"
+ dependencies:
+ get-proxy "^1.0.1"
+ is-obj "^1.0.0"
+ object-assign "^3.0.0"
+ tunnel-agent "^0.4.0"
+
+center-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
+ dependencies:
+ align-text "^0.1.3"
+ lazy-cache "^1.0.3"
+
+chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chokidar@^1.0.0:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
+ dependencies:
+ anymatch "^1.3.0"
+ async-each "^1.0.0"
+ glob-parent "^2.0.0"
+ inherits "^2.0.1"
+ is-binary-path "^1.0.0"
+ is-glob "^2.0.0"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.0.0"
+ optionalDependencies:
+ fsevents "^1.0.0"
+
+cipher-base@^1.0.0, cipher-base@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07"
+ dependencies:
+ inherits "^2.0.1"
+
+clap@^1.0.9:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b"
+ dependencies:
+ chalk "^1.1.3"
+
+clean-css@~3.4.2:
+ version "3.4.25"
+ resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.25.tgz#9e9a52d5c1e6bc5123e1b2783fa65fe958946ede"
+ dependencies:
+ commander "2.8.x"
+ source-map "0.4.x"
+
+cliui@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+ dependencies:
+ center-align "^0.1.1"
+ right-align "^0.1.1"
+ wordwrap "0.0.2"
+
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
+clone-stats@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
+
+clone@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f"
+
+clone@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+co@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78"
+
+coa@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.1.tgz#7f959346cfc8719e3f7233cd6852854a7c67d8a3"
+ dependencies:
+ q "^1.1.2"
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+
+coffee-script@~1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.10.0.tgz#12938bcf9be1948fa006f92e0c4c9e81705108c0"
+
+colors@~0.6.0-1:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
+
+colors@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
+
+combine-source-map@~0.7.1:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e"
+ dependencies:
+ convert-source-map "~1.1.0"
+ inline-source-map "~0.6.0"
+ lodash.memoize "~3.0.3"
+ source-map "~0.5.3"
+
+combined-stream@^1.0.5, combined-stream@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781"
+
+commander@~2.8.1, commander@2.8.x:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
+ dependencies:
+ graceful-readlink ">= 1.0.0"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+concat-stream@^1.4.1, concat-stream@^1.4.6, concat-stream@^1.4.7:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
+ dependencies:
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+concat-stream@~1.5.0, concat-stream@~1.5.1:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "~2.0.0"
+ typedarray "~0.0.5"
+
+console-browserify@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
+ dependencies:
+ date-now "^0.1.4"
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+
+console-stream@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/console-stream/-/console-stream-0.1.1.tgz#a095fe07b20465955f2fafd28b5d72bccd949d44"
+
+constants-browserify@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
+
+content-type@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
+
+convert-source-map@^1.1.1:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.4.0.tgz#e3dad195bf61bfe13a7a3c73e9876ec14a0268f3"
+
+convert-source-map@~1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+create-ecdh@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
+ dependencies:
+ bn.js "^4.1.0"
+ elliptic "^6.0.0"
+
+create-error-class@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
+ dependencies:
+ capture-stack-trace "^1.0.0"
+
+create-hash@^1.1.0, create-hash@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.2.tgz#51210062d7bb7479f6c65bb41a92208b1d61abad"
+ dependencies:
+ cipher-base "^1.0.1"
+ inherits "^2.0.1"
+ ripemd160 "^1.0.0"
+ sha.js "^2.3.6"
+
+create-hmac@^1.1.0, create-hmac@^1.1.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.4.tgz#d3fb4ba253eb8b3f56e39ea2fbcb8af747bd3170"
+ dependencies:
+ create-hash "^1.1.0"
+ inherits "^2.0.1"
+
+cross-spawn@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
+ dependencies:
+ lru-cache "^4.0.1"
+ which "^1.2.9"
+
+cryptiles@2.x.x:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
+ dependencies:
+ boom "2.x.x"
+
+crypto-browserify@^3.0.0:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522"
+ dependencies:
+ browserify-cipher "^1.0.0"
+ browserify-sign "^4.0.0"
+ create-ecdh "^4.0.0"
+ create-hash "^1.1.0"
+ create-hmac "^1.1.0"
+ diffie-hellman "^5.0.0"
+ inherits "^2.0.1"
+ pbkdf2 "^3.0.3"
+ public-encrypt "^4.0.0"
+ randombytes "^2.0.0"
+
+csso@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-2.0.0.tgz#178b43a44621221c27756086f531e02f42900ee8"
+ dependencies:
+ clap "^1.0.9"
+ source-map "^0.5.3"
+
+currently-unhandled@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
+ dependencies:
+ array-find-index "^1.0.1"
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ dependencies:
+ assert-plus "^1.0.0"
+
+date-now@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
+
+dateformat@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17"
+
+dateformat@~1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
+ dependencies:
+ get-stdin "^4.0.1"
+ meow "^3.3.0"
+
+debug@^2.2.0:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d"
+ dependencies:
+ ms "0.7.2"
+
+debug@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
+ dependencies:
+ ms "0.7.1"
+
+decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+
+decompress-tar@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-3.1.0.tgz#217c789f9b94450efaadc5c5e537978fc333c466"
+ dependencies:
+ is-tar "^1.0.0"
+ object-assign "^2.0.0"
+ strip-dirs "^1.0.0"
+ tar-stream "^1.1.1"
+ through2 "^0.6.1"
+ vinyl "^0.4.3"
+
+decompress-tarbz2@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz#8b23935681355f9f189d87256a0f8bdd96d9666d"
+ dependencies:
+ is-bzip2 "^1.0.0"
+ object-assign "^2.0.0"
+ seek-bzip "^1.0.3"
+ strip-dirs "^1.0.0"
+ tar-stream "^1.1.1"
+ through2 "^0.6.1"
+ vinyl "^0.4.3"
+
+decompress-targz@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-3.1.0.tgz#b2c13df98166268991b715d6447f642e9696f5a0"
+ dependencies:
+ is-gzip "^1.0.0"
+ object-assign "^2.0.0"
+ strip-dirs "^1.0.0"
+ tar-stream "^1.1.1"
+ through2 "^0.6.1"
+ vinyl "^0.4.3"
+
+decompress-unzip@^3.0.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-3.4.0.tgz#61475b4152066bbe3fee12f9d629d15fe6478eeb"
+ dependencies:
+ is-zip "^1.0.0"
+ read-all-stream "^3.0.0"
+ stat-mode "^0.2.0"
+ strip-dirs "^1.0.0"
+ through2 "^2.0.0"
+ vinyl "^1.0.0"
+ yauzl "^2.2.1"
+
+decompress@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/decompress/-/decompress-3.0.0.tgz#af1dd50d06e3bfc432461d37de11b38c0d991bed"
+ dependencies:
+ buffer-to-vinyl "^1.0.0"
+ concat-stream "^1.4.6"
+ decompress-tar "^3.0.0"
+ decompress-tarbz2 "^3.0.0"
+ decompress-targz "^3.0.0"
+ decompress-unzip "^3.0.0"
+ stream-combiner2 "^1.1.1"
+ vinyl-assign "^1.0.1"
+ vinyl-fs "^2.2.0"
+
+deep-extend@~0.4.0:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253"
+
+define-properties@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
+ dependencies:
+ foreach "^2.0.5"
+ object-keys "^1.0.8"
+
+defined@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+
+depd@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
+
+deps-sort@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5"
+ dependencies:
+ JSONStream "^1.0.3"
+ shasum "^1.0.0"
+ subarg "^1.0.0"
+ through2 "^2.0.0"
+
+des.js@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
+ dependencies:
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+
+detective@^4.0.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1"
+ dependencies:
+ acorn "^4.0.3"
+ defined "^1.0.0"
+
+diff@^2.0.2:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
+
+diffie-hellman@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
+ dependencies:
+ bn.js "^4.1.0"
+ miller-rabin "^4.0.0"
+ randombytes "^2.0.0"
+
+domain-browser@~1.1.0:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
+
+download@^4.0.0, download@^4.1.2:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/download/-/download-4.4.3.tgz#aa55fdad392d95d4b68e8c2be03e0c2aa21ba9ac"
+ dependencies:
+ caw "^1.0.1"
+ concat-stream "^1.4.7"
+ each-async "^1.0.0"
+ filenamify "^1.0.1"
+ got "^5.0.0"
+ gulp-decompress "^1.2.0"
+ gulp-rename "^1.2.0"
+ is-url "^1.2.0"
+ object-assign "^4.0.1"
+ read-all-stream "^3.0.0"
+ readable-stream "^2.0.2"
+ stream-combiner2 "^1.1.1"
+ vinyl "^1.0.0"
+ vinyl-fs "^2.2.0"
+ ware "^1.2.0"
+
+duplexer2@^0.1.2, duplexer2@^0.1.4, duplexer2@~0.1.0, duplexer2@~0.1.2:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
+ dependencies:
+ readable-stream "^2.0.2"
+
+duplexer2@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db"
+ dependencies:
+ readable-stream "~1.1.9"
+
+duplexify@^3.2.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604"
+ dependencies:
+ end-of-stream "1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
+each-async@^1.0.0, each-async@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473"
+ dependencies:
+ onetime "^1.0.0"
+ set-immediate-shim "^1.0.0"
+
+ecc-jsbn@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
+ dependencies:
+ jsbn "~0.1.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+electron-to-chromium@^1.2.7:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.2.8.tgz#22c2e6200d350da27d6050db7e3f6f85d18cf4ed"
+
+elliptic@^6.0.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
+ dependencies:
+ bn.js "^4.4.0"
+ brorand "^1.0.1"
+ hash.js "^1.0.0"
+ hmac-drbg "^1.0.0"
+ inherits "^2.0.1"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.0"
+
+end-of-stream@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206"
+ dependencies:
+ once "^1.4.0"
+
+end-of-stream@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e"
+ dependencies:
+ once "~1.3.0"
+
+error-ex@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
+ dependencies:
+ is-arrayish "^0.2.1"
+
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+esprima@^2.6.0:
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+
+esprima@~1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad"
+
+eventemitter2@~0.4.13:
+ version "0.4.14"
+ resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab"
+
+events@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+
+evp_bytestokey@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53"
+ dependencies:
+ create-hash "^1.1.1"
+
+exec-buffer@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/exec-buffer/-/exec-buffer-2.0.1.tgz#0028a31be0b1460b61d075f96af4583b9e335ea0"
+ dependencies:
+ rimraf "^2.2.6"
+ tempfile "^1.0.0"
+
+exec-series@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/exec-series/-/exec-series-1.0.3.tgz#6d257a9beac482a872c7783bc8615839fc77143a"
+ dependencies:
+ async-each-series "^1.1.0"
+ object-assign "^4.1.0"
+
+executable@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/executable/-/executable-1.1.0.tgz#877980e9112f3391066da37265de7ad8434ab4d9"
+ dependencies:
+ meow "^3.1.0"
+
+exit@~0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
+
+expand-brackets@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+ dependencies:
+ is-posix-bracket "^0.1.0"
+
+expand-range@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
+ dependencies:
+ fill-range "^2.1.0"
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend@^3.0.0, extend@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
+
+extglob@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
+ dependencies:
+ is-extglob "^1.0.0"
+
+extsprintf@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+
+fancy-log@^1.1.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948"
+ dependencies:
+ chalk "^1.1.1"
+ time-stamp "^1.0.0"
+
+faye-websocket@~0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+fd-slicer@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
+ dependencies:
+ pend "~1.2.0"
+
+figures@^1.0.1, figures@^1.3.5:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+ dependencies:
+ escape-string-regexp "^1.0.5"
+ object-assign "^4.1.0"
+
+file-type@^3.1.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
+
+filename-regex@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
+
+filename-reserved-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz#e61cf805f0de1c984567d0386dc5df50ee5af7e4"
+
+filenamify@^1.0.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-1.2.1.tgz#a9f2ffd11c503bed300015029272378f1f1365a5"
+ dependencies:
+ filename-reserved-regex "^1.0.0"
+ strip-outer "^1.0.0"
+ trim-repeated "^1.0.0"
+
+fill-range@^2.1.0:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
+ dependencies:
+ is-number "^2.1.0"
+ isobject "^2.0.0"
+ randomatic "^1.1.3"
+ repeat-element "^1.1.2"
+ repeat-string "^1.5.2"
+
+find-up@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+ dependencies:
+ path-exists "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+find-versions@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-1.2.1.tgz#cbde9f12e38575a0af1be1b9a2c5d5fd8f186b62"
+ dependencies:
+ array-uniq "^1.0.0"
+ get-stdin "^4.0.1"
+ meow "^3.5.0"
+ semver-regex "^1.0.0"
+
+findup-sync@~0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16"
+ dependencies:
+ glob "~5.0.0"
+
+findup@^0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/findup/-/findup-0.1.5.tgz#8ad929a3393bac627957a7e5de4623b06b0e2ceb"
+ dependencies:
+ colors "~0.6.0-1"
+ commander "~2.1.0"
+
+first-chunk-stream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e"
+
+for-in@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+
+for-own@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
+ dependencies:
+ for-in "^1.0.1"
+
+foreach@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+
+form-data@~2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.12"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+fsevents@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.1.tgz#f19fd28f43eeaf761680e519a203c4d0b3d31aff"
+ dependencies:
+ nan "^2.3.0"
+ node-pre-gyp "^0.6.29"
+
+fstream-ignore@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
+ dependencies:
+ fstream "^1.0.0"
+ inherits "2"
+ minimatch "^3.0.0"
+
+fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
+function-bind@^1.0.2, function-bind@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
+
+gauge@~2.7.1:
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09"
+ dependencies:
+ aproba "^1.0.3"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.0"
+ object-assign "^4.1.0"
+ signal-exit "^3.0.0"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wide-align "^1.1.0"
+
+gaze@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105"
+ dependencies:
+ globule "^1.0.0"
+
+get-caller-file@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+
+get-proxy@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-1.1.0.tgz#894854491bc591b0f147d7ae570f5c678b7256eb"
+ dependencies:
+ rc "^1.1.2"
+
+get-stdin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+
+getobject@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/getobject/-/getobject-0.1.0.tgz#047a449789fa160d018f5486ed91320b6ec7885c"
+
+getpass@^0.1.1:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
+ dependencies:
+ assert-plus "^1.0.0"
+
+gifsicle@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/gifsicle/-/gifsicle-3.0.4.tgz#f45cb5ed10165b665dc929e0e9328b6c821dfa3b"
+ dependencies:
+ bin-build "^2.0.0"
+ bin-wrapper "^3.0.0"
+ logalot "^2.0.0"
+
+glob-base@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
+ dependencies:
+ glob-parent "^2.0.0"
+ is-glob "^2.0.0"
+
+glob-parent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
+ dependencies:
+ is-glob "^2.0.0"
+
+glob-parent@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
+ dependencies:
+ is-glob "^3.1.0"
+ path-dirname "^1.0.0"
+
+glob-stream@^5.3.2:
+ version "5.3.5"
+ resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22"
+ dependencies:
+ extend "^3.0.0"
+ glob "^5.0.3"
+ glob-parent "^3.0.0"
+ micromatch "^2.3.7"
+ ordered-read-streams "^0.3.0"
+ through2 "^0.6.0"
+ to-absolute-glob "^0.1.1"
+ unique-stream "^2.0.2"
+
+glob@^5.0.3, glob@~5.0.0:
+ version "5.0.15"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^6.0.3:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@~7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.2"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+glob@~7.0.0:
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.2"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globule@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/globule/-/globule-1.1.0.tgz#c49352e4dc183d85893ee825385eb994bb6df45f"
+ dependencies:
+ glob "~7.1.1"
+ lodash "~4.16.4"
+ minimatch "~3.0.2"
+
+glogg@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5"
+ dependencies:
+ sparkles "^1.0.0"
+
+got@^5.0.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
+ dependencies:
+ create-error-class "^3.0.1"
+ duplexer2 "^0.1.4"
+ is-redirect "^1.0.0"
+ is-retry-allowed "^1.0.0"
+ is-stream "^1.0.0"
+ lowercase-keys "^1.0.0"
+ node-status-codes "^1.0.0"
+ object-assign "^4.0.1"
+ parse-json "^2.1.0"
+ pinkie-promise "^2.0.0"
+ read-all-stream "^3.0.0"
+ readable-stream "^2.0.5"
+ timed-out "^3.0.0"
+ unzip-response "^1.0.2"
+ url-parse-lax "^1.0.0"
+
+graceful-fs@^4.0.0, graceful-fs@^4.1.2:
+ version "4.1.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
+
+"graceful-readlink@>= 1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
+
+grunt-browserify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/grunt-browserify/-/grunt-browserify-5.0.0.tgz#1d333ca98bdac44576f646e0d6680087cbf8c615"
+ dependencies:
+ async "^1.5.0"
+ browserify "^13.0.0"
+ glob "^6.0.3"
+ lodash "^3.10.1"
+ resolve "^1.1.6"
+ watchify "^3.6.1"
+
+grunt-cli@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8"
+ dependencies:
+ findup-sync "~0.3.0"
+ grunt-known-options "~1.1.0"
+ nopt "~3.0.6"
+ resolve "~1.1.0"
+
+grunt-contrib-cssmin@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/grunt-contrib-cssmin/-/grunt-contrib-cssmin-1.0.2.tgz#1734cbd3d84ca7364758b7e58ff18e52aa60bb76"
+ dependencies:
+ chalk "^1.0.0"
+ clean-css "~3.4.2"
+ maxmin "^1.1.0"
+
+grunt-contrib-imagemin@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/grunt-contrib-imagemin/-/grunt-contrib-imagemin-1.0.1.tgz#e47a35613376f4caa9c1f90446503cae1c944d79"
+ dependencies:
+ async "^1.5.2"
+ chalk "^1.0.0"
+ gulp-rename "^1.2.0"
+ imagemin "^4.0.0"
+ pretty-bytes "^3.0.1"
+
+grunt-contrib-uglify@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/grunt-contrib-uglify/-/grunt-contrib-uglify-2.2.0.tgz#9e67c2469b4774daa3345840511d51fc4fb34f19"
+ dependencies:
+ chalk "^1.0.0"
+ maxmin "^1.1.0"
+ object.assign "^4.0.4"
+ uglify-js "~2.8.3"
+ uri-path "^1.0.0"
+
+grunt-contrib-watch@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz#84a1a7a1d6abd26ed568413496c73133e990018f"
+ dependencies:
+ async "^1.5.0"
+ gaze "^1.0.0"
+ lodash "^3.10.1"
+ tiny-lr "^0.2.1"
+
+grunt-jsvalidate@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/grunt-jsvalidate/-/grunt-jsvalidate-0.2.2.tgz#fd094425888d6e63dfaa06cfb09ee052b8ebbe8f"
+ dependencies:
+ esprima "~1.0.0"
+
+grunt-known-options@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.0.tgz#a4274eeb32fa765da5a7a3b1712617ce3b144149"
+
+grunt-legacy-log-utils@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz#a7b8e2d0fb35b5a50f4af986fc112749ebc96f3d"
+ dependencies:
+ chalk "~1.1.1"
+ lodash "~4.3.0"
+
+grunt-legacy-log@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz#fb86f1809847bc07dc47843f9ecd6cacb62df2d5"
+ dependencies:
+ colors "~1.1.2"
+ grunt-legacy-log-utils "~1.0.0"
+ hooker "~0.2.3"
+ lodash "~3.10.1"
+ underscore.string "~3.2.3"
+
+grunt-legacy-util@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz#386aa78dc6ed50986c2b18957265b1b48abb9b86"
+ dependencies:
+ async "~1.5.2"
+ exit "~0.1.1"
+ getobject "~0.1.0"
+ hooker "~0.2.3"
+ lodash "~4.3.0"
+ underscore.string "~3.2.3"
+ which "~1.2.1"
+
+grunt-newer@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/grunt-newer/-/grunt-newer-1.2.0.tgz#77f81e6afb7a82b3f91c14137361998d58866841"
+ dependencies:
+ async "^1.5.2"
+ rimraf "^2.5.2"
+
+grunt-openport@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/grunt-openport/-/grunt-openport-1.0.0.tgz#fc0a1fc9488d3c19c823fbcb88a75e0fccb061f5"
+ dependencies:
+ openport "0.0.4"
+
+grunt-postcss@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/grunt-postcss/-/grunt-postcss-0.8.0.tgz#8f30a8af607903ce0c45f01f0be42c60e31ceb0e"
+ dependencies:
+ chalk "^1.0.0"
+ diff "^2.0.2"
+ postcss "^5.0.0"
+
+grunt-rtlcss@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/grunt-rtlcss/-/grunt-rtlcss-2.0.1.tgz#e9e61ce437406397f9e3a4b1bf669e47e71ffcc3"
+ dependencies:
+ chalk "^1.0.0"
+ rtlcss "^2.0.0"
+
+grunt-sass@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-1.1.0.tgz#480533c751a617366385c0fb9845184c05b2e4ab"
+ dependencies:
+ each-async "^1.0.0"
+ node-sass "^3.4.0"
+ object-assign "^4.0.1"
+
+grunt@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/grunt/-/grunt-1.0.1.tgz#e8778764e944b18f32bb0f10b9078475c9dfb56b"
+ dependencies:
+ coffee-script "~1.10.0"
+ dateformat "~1.0.12"
+ eventemitter2 "~0.4.13"
+ exit "~0.1.1"
+ findup-sync "~0.3.0"
+ glob "~7.0.0"
+ grunt-cli "~1.2.0"
+ grunt-known-options "~1.1.0"
+ grunt-legacy-log "~1.0.0"
+ grunt-legacy-util "~1.0.0"
+ iconv-lite "~0.4.13"
+ js-yaml "~3.5.2"
+ minimatch "~3.0.0"
+ nopt "~3.0.6"
+ path-is-absolute "~1.0.0"
+ rimraf "~2.2.8"
+
+gulp-decompress@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/gulp-decompress/-/gulp-decompress-1.2.0.tgz#8eeb65a5e015f8ed8532cafe28454960626f0dc7"
+ dependencies:
+ archive-type "^3.0.0"
+ decompress "^3.0.0"
+ gulp-util "^3.0.1"
+ readable-stream "^2.0.2"
+
+gulp-rename@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.2.2.tgz#3ad4428763f05e2764dec1c67d868db275687817"
+
+gulp-sourcemaps@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c"
+ dependencies:
+ convert-source-map "^1.1.1"
+ graceful-fs "^4.1.2"
+ strip-bom "^2.0.0"
+ through2 "^2.0.0"
+ vinyl "^1.0.0"
+
+gulp-util@^3.0.1:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f"
+ dependencies:
+ array-differ "^1.0.0"
+ array-uniq "^1.0.2"
+ beeper "^1.0.0"
+ chalk "^1.0.0"
+ dateformat "^2.0.0"
+ fancy-log "^1.1.0"
+ gulplog "^1.0.0"
+ has-gulplog "^0.1.0"
+ lodash._reescape "^3.0.0"
+ lodash._reevaluate "^3.0.0"
+ lodash._reinterpolate "^3.0.0"
+ lodash.template "^3.0.0"
+ minimist "^1.1.0"
+ multipipe "^0.1.2"
+ object-assign "^3.0.0"
+ replace-ext "0.0.1"
+ through2 "^2.0.0"
+ vinyl "^0.5.0"
+
+gulplog@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5"
+ dependencies:
+ glogg "^1.0.0"
+
+gzip-size@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-1.0.0.tgz#66cf8b101047227b95bace6ea1da0c177ed5c22f"
+ dependencies:
+ browserify-zlib "^0.1.4"
+ concat-stream "^1.4.1"
+
+har-schema@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
+
+har-validator@~4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
+ dependencies:
+ ajv "^4.9.1"
+ har-schema "^1.0.5"
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+has-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+
+has-gulplog@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce"
+ dependencies:
+ sparkles "^1.0.0"
+
+has-unicode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+
+has@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
+hash.js@^1.0.0, hash.js@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573"
+ dependencies:
+ inherits "^2.0.1"
+
+hawk@~3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
+ dependencies:
+ boom "2.x.x"
+ cryptiles "2.x.x"
+ hoek "2.x.x"
+ sntp "1.x.x"
+
+hmac-drbg@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.0.tgz#3db471f45aae4a994a0688322171f51b8b91bee5"
+ dependencies:
+ hash.js "^1.0.3"
+ minimalistic-assert "^1.0.0"
+ minimalistic-crypto-utils "^1.0.1"
+
+hoek@2.x.x:
+ version "2.16.3"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+
+hooker@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959"
+
+hosted-git-info@^2.1.4:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.1.tgz#4b0445e41c004a8bd1337773a4ff790ca40318c8"
+
+htmlescape@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
+
+http-errors@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.3.1.tgz#197e22cdebd4198585e8694ef6786197b91ed942"
+ dependencies:
+ inherits "~2.0.1"
+ statuses "1"
+
+http-signature@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
+ dependencies:
+ assert-plus "^0.2.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+https-browserify@~0.0.0:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
+
+iconv-lite@~0.4.13:
+ version "0.4.15"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
+
+iconv-lite@0.4.13:
+ version "0.4.13"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
+
+ieee754@^1.1.4:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+
+imagemin-gifsicle@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/imagemin-gifsicle/-/imagemin-gifsicle-4.2.0.tgz#0fef9bbad3476e6b76885736cc5b0b87a08757ca"
+ dependencies:
+ gifsicle "^3.0.0"
+ is-gif "^1.0.0"
+ through2 "^0.6.1"
+
+imagemin-jpegtran@^4.0.0:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/imagemin-jpegtran/-/imagemin-jpegtran-4.3.2.tgz#1bc6d1e2bd13fdb64d245526d635a7e5dfeb12fc"
+ dependencies:
+ is-jpg "^1.0.0"
+ jpegtran-bin "^3.0.0"
+ through2 "^2.0.0"
+
+imagemin-optipng@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/imagemin-optipng/-/imagemin-optipng-4.3.0.tgz#7604663ab2ee315733274726fd1c374d2b44adb6"
+ dependencies:
+ exec-buffer "^2.0.0"
+ is-png "^1.0.0"
+ optipng-bin "^3.0.0"
+ through2 "^0.6.1"
+
+imagemin-svgo@^4.0.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/imagemin-svgo/-/imagemin-svgo-4.2.1.tgz#54f07dc56f47260462df6a61c54befb44b57be55"
+ dependencies:
+ is-svg "^1.0.0"
+ svgo "^0.6.0"
+ through2 "^2.0.0"
+
+imagemin@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/imagemin/-/imagemin-4.0.0.tgz#e90e7f0936836595f18fa15fe906f4fa259ea847"
+ dependencies:
+ buffer-to-vinyl "^1.0.0"
+ concat-stream "^1.4.6"
+ optional "^0.1.0"
+ readable-stream "^2.0.0"
+ stream-combiner2 "^1.1.1"
+ vinyl-fs "^2.1.1"
+ optionalDependencies:
+ imagemin-gifsicle "^4.0.0"
+ imagemin-jpegtran "^4.0.0"
+ imagemin-optipng "^4.0.0"
+ imagemin-svgo "^4.0.0"
+
+in-publish@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
+
+indent-string@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+ dependencies:
+ repeating "^2.0.0"
+
+indexof@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+inherits@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
+
+ini@~1.3.0:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
+
+inline-source-map@~0.6.0:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5"
+ dependencies:
+ source-map "~0.5.3"
+
+insert-module-globals@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3"
+ dependencies:
+ combine-source-map "~0.7.1"
+ concat-stream "~1.5.1"
+ is-buffer "^1.1.0"
+ JSONStream "^1.0.3"
+ lexical-scope "^1.2.0"
+ process "~0.11.0"
+ through2 "^2.0.0"
+ xtend "^4.0.0"
+
+invert-kv@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+
+ip-regex@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd"
+
+is-absolute@^0.1.5:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.1.7.tgz#847491119fccb5fb436217cc737f7faad50f603f"
+ dependencies:
+ is-relative "^0.1.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+
+is-binary-path@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
+ dependencies:
+ binary-extensions "^1.0.0"
+
+is-buffer@^1.0.2, is-buffer@^1.1.0:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
+
+is-builtin-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+ dependencies:
+ builtin-modules "^1.0.0"
+
+is-bzip2@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-bzip2/-/is-bzip2-1.0.0.tgz#5ee58eaa5a2e9c80e21407bedf23ae5ac091b3fc"
+
+is-dotfile@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d"
+
+is-equal-shallow@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
+ dependencies:
+ is-primitive "^2.0.0"
+
+is-extendable@^0.1.0, is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+
+is-extglob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+
+is-extglob@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+
+is-finite@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-gif@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-gif/-/is-gif-1.0.0.tgz#a6d2ae98893007bffa97a1d8c01d63205832097e"
+
+is-glob@^2.0.0, is-glob@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+ dependencies:
+ is-extglob "^1.0.0"
+
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ dependencies:
+ is-extglob "^2.1.0"
+
+is-gzip@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
+
+is-jpg@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-jpg/-/is-jpg-1.0.0.tgz#2959c17e73430db38264da75b90dd54f2d86da1c"
+
+is-natural-number@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-2.1.1.tgz#7d4c5728377ef386c3e194a9911bf57c6dc335e7"
+
+is-number@^2.0.2, is-number@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-obj@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
+
+is-png@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-png/-/is-png-1.0.0.tgz#3d80373fe9b89d65fd341f659d3fc0a1135e718a"
+
+is-posix-bracket@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
+
+is-primitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+
+is-redirect@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
+
+is-relative@^0.1.0:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.1.3.tgz#905fee8ae86f45b3ec614bc3c15c869df0876e82"
+
+is-retry-allowed@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
+
+is-stream@^1.0.0, is-stream@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+
+is-svg@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-1.1.1.tgz#ac0efaafb653ac58473708b1f873636ca110e31b"
+
+is-tar@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-tar/-/is-tar-1.0.0.tgz#2f6b2e1792c1f5bb36519acaa9d65c0d26fe853d"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+is-url@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.2.tgz#498905a593bf47cc2d9e7f738372bbf7696c7f26"
+
+is-utf8@^0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
+
+is-valid-glob@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe"
+
+is-zip@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-zip/-/is-zip-1.0.0.tgz#47b0a8ff4d38a76431ccfd99a8e15a4c86ba2325"
+
+isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isarray@~0.0.1, isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ dependencies:
+ isarray "1.0.0"
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+
+jodid25519@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967"
+ dependencies:
+ jsbn "~0.1.0"
+
+jpegtran-bin@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/jpegtran-bin/-/jpegtran-bin-3.2.0.tgz#f60ecf4ae999c0bdad2e9fbcdf2b6f0981e7a29b"
+ dependencies:
+ bin-build "^2.0.0"
+ bin-wrapper "^3.0.0"
+ logalot "^2.0.0"
+
+js-base64@^2.1.9:
+ version "2.1.9"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
+
+js-yaml@~3.5.2:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.5.5.tgz#0377c38017cabc7322b0d1fbcd25a491641f2fbe"
+ dependencies:
+ argparse "^1.0.2"
+ esprima "^2.6.0"
+
+js-yaml@~3.6.0:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^2.6.0"
+
+jsbn@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+
+json-schema@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+
+json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ dependencies:
+ jsonify "~0.0.0"
+
+json-stable-stringify@~0.0.0:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45"
+ dependencies:
+ jsonify "~0.0.0"
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+
+jsonparse@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.0.tgz#85fc245b1d9259acc6941960b905adf64e7de0e8"
+
+JSONStream@^1.0.3:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
+ dependencies:
+ jsonparse "^1.2.0"
+ through ">=2.2.7 <3"
+
+jsprim@^1.2.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
+ dependencies:
+ assert-plus "1.0.0"
+ extsprintf "1.0.2"
+ json-schema "0.2.3"
+ verror "1.3.6"
+
+kind-of@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
+ dependencies:
+ is-buffer "^1.0.2"
+
+labeled-stream-splicer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz#a52e1d138024c00b86b1c0c91f677918b8ae0a59"
+ dependencies:
+ inherits "^2.0.1"
+ isarray "~0.0.1"
+ stream-splicer "^2.0.0"
+
+lazy-cache@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
+
+lazy-req@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac"
+
+lazystream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
+ dependencies:
+ readable-stream "^2.0.5"
+
+lcid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+ dependencies:
+ invert-kv "^1.0.0"
+
+lexical-scope@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4"
+ dependencies:
+ astw "^2.0.0"
+
+livereload-js@^2.2.0:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.2.2.tgz#6c87257e648ab475bc24ea257457edcc1f8d0bc2"
+
+load-json-file@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ strip-bom "^2.0.0"
+
+lodash._basecopy@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
+
+lodash._basetostring@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5"
+
+lodash._basevalues@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7"
+
+lodash._getnative@^3.0.0:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+
+lodash._isiterateecall@^3.0.0:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
+
+lodash._reescape@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a"
+
+lodash._reevaluate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed"
+
+lodash._reinterpolate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
+
+lodash._root@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
+
+lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
+
+lodash.clonedeep@^4.3.2:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+
+lodash.escape@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698"
+ dependencies:
+ lodash._root "^3.0.0"
+
+lodash.isarguments@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+
+lodash.isarray@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+
+lodash.isequal@^4.0.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+
+lodash.keys@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+ dependencies:
+ lodash._getnative "^3.0.0"
+ lodash.isarguments "^3.0.0"
+ lodash.isarray "^3.0.0"
+
+lodash.memoize@~3.0.3:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
+
+lodash.restparam@^3.0.0:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
+
+lodash.template@^3.0.0:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f"
+ dependencies:
+ lodash._basecopy "^3.0.0"
+ lodash._basetostring "^3.0.0"
+ lodash._basevalues "^3.0.0"
+ lodash._isiterateecall "^3.0.0"
+ lodash._reinterpolate "^3.0.0"
+ lodash.escape "^3.0.0"
+ lodash.keys "^3.0.0"
+ lodash.restparam "^3.0.0"
+ lodash.templatesettings "^3.0.0"
+
+lodash.templatesettings@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5"
+ dependencies:
+ lodash._reinterpolate "^3.0.0"
+ lodash.escape "^3.0.0"
+
+lodash@^3.10.1, lodash@~3.10.1:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
+
+lodash@^4.0.0:
+ version "4.17.4"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+lodash@~4.16.4:
+ version "4.16.6"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
+
+lodash@~4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.3.0.tgz#efd9c4a6ec53f3b05412429915c3e4824e4d25a4"
+
+logalot@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/logalot/-/logalot-2.1.0.tgz#5f8e8c90d304edf12530951a5554abb8c5e3f552"
+ dependencies:
+ figures "^1.3.5"
+ squeak "^1.0.0"
+
+longest@^1.0.0, longest@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
+
+loud-rejection@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
+ dependencies:
+ currently-unhandled "^0.4.1"
+ signal-exit "^3.0.0"
+
+lowercase-keys@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
+
+lpad-align@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/lpad-align/-/lpad-align-1.1.0.tgz#27fa786bcb695fc434ea1500723eb8d0bdc82bf4"
+ dependencies:
+ get-stdin "^4.0.1"
+ longest "^1.0.0"
+ lpad "^2.0.1"
+ meow "^3.3.0"
+
+lpad@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/lpad/-/lpad-2.0.1.tgz#28316b4e7b2015f511f6591459afc0e5944008ad"
+
+lru-cache@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
+ dependencies:
+ pseudomap "^1.0.1"
+ yallist "^2.0.0"
+
+map-obj@^1.0.0, map-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
+
+matchdep@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-1.0.1.tgz#a57a33804491fbae208aba8f68380437abc2dca5"
+ dependencies:
+ findup-sync "~0.3.0"
+ micromatch "^2.3.7"
+ resolve "~1.1.6"
+ stack-trace "0.0.9"
+
+maxmin@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/maxmin/-/maxmin-1.1.0.tgz#71365e84a99dd8f8b3f7d5fde2f00d1e7f73be61"
+ dependencies:
+ chalk "^1.0.0"
+ figures "^1.0.1"
+ gzip-size "^1.0.0"
+ pretty-bytes "^1.0.0"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+meow@^3.1.0, meow@^3.3.0, meow@^3.5.0, meow@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
+ dependencies:
+ camelcase-keys "^2.0.0"
+ decamelize "^1.1.2"
+ loud-rejection "^1.0.0"
+ map-obj "^1.0.1"
+ minimist "^1.1.3"
+ normalize-package-data "^2.3.4"
+ object-assign "^4.0.1"
+ read-pkg-up "^1.0.1"
+ redent "^1.0.0"
+ trim-newlines "^1.0.0"
+
+merge-stream@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
+ dependencies:
+ readable-stream "^2.0.1"
+
+micromatch@^2.1.5, micromatch@^2.3.7:
+ version "2.3.11"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
+ dependencies:
+ arr-diff "^2.0.0"
+ array-unique "^0.2.1"
+ braces "^1.8.2"
+ expand-brackets "^0.1.4"
+ extglob "^0.3.1"
+ filename-regex "^2.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.1"
+ kind-of "^3.0.2"
+ normalize-path "^2.0.1"
+ object.omit "^2.0.0"
+ parse-glob "^3.0.4"
+ regex-cache "^0.4.2"
+
+miller-rabin@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d"
+ dependencies:
+ bn.js "^4.0.0"
+ brorand "^1.0.1"
+
+mime-db@~1.26.0:
+ version "1.26.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
+
+mime-types@^2.1.12, mime-types@~2.1.13, mime-types@~2.1.7:
+ version "2.1.14"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
+ dependencies:
+ mime-db "~1.26.0"
+
+minimalistic-assert@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
+
+minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
+
+minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.0, minimatch@~3.0.2, "minimatch@2 || 3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
+ dependencies:
+ brace-expansion "^1.0.0"
+
+minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ dependencies:
+ minimist "0.0.8"
+
+module-deps@^4.0.8:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-4.1.1.tgz#23215833f1da13fd606ccb8087b44852dcb821fd"
+ dependencies:
+ browser-resolve "^1.7.0"
+ cached-path-relative "^1.0.0"
+ concat-stream "~1.5.0"
+ defined "^1.0.0"
+ detective "^4.0.0"
+ duplexer2 "^0.1.2"
+ inherits "^2.0.1"
+ JSONStream "^1.0.3"
+ parents "^1.0.0"
+ readable-stream "^2.0.2"
+ resolve "^1.1.3"
+ stream-combiner2 "^1.1.1"
+ subarg "^1.0.0"
+ through2 "^2.0.0"
+ xtend "^4.0.0"
+
+ms@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
+
+ms@0.7.2:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+
+multipipe@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b"
+ dependencies:
+ duplexer2 "0.0.2"
+
+nan@^2.3.0, nan@^2.3.2:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2"
+
+node-gyp@^3.3.1:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.0.tgz#7474f63a3a0501161dda0b6341f022f14c423fa6"
+ dependencies:
+ fstream "^1.0.0"
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "2"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^2.0.0"
+ which "1"
+
+node-pre-gyp@^0.6.29:
+ version "0.6.34"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz#94ad1c798a11d7fc67381b50d47f8cc18d9799f7"
+ dependencies:
+ mkdirp "^0.5.1"
+ nopt "^4.0.1"
+ npmlog "^4.0.2"
+ rc "^1.1.7"
+ request "^2.81.0"
+ rimraf "^2.6.1"
+ semver "^5.3.0"
+ tar "^2.2.1"
+ tar-pack "^3.4.0"
+
+node-sass@^3.4.0:
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-3.13.1.tgz#7240fbbff2396304b4223527ed3020589c004fc2"
+ dependencies:
+ async-foreach "^0.1.3"
+ chalk "^1.1.1"
+ cross-spawn "^3.0.0"
+ gaze "^1.0.0"
+ get-stdin "^4.0.1"
+ glob "^7.0.3"
+ in-publish "^2.0.0"
+ lodash.assign "^4.2.0"
+ lodash.clonedeep "^4.3.2"
+ meow "^3.7.0"
+ mkdirp "^0.5.1"
+ nan "^2.3.2"
+ node-gyp "^3.3.1"
+ npmlog "^4.0.0"
+ request "^2.61.0"
+ sass-graph "^2.1.1"
+
+node-status-codes@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f"
+
+nopt@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
+ dependencies:
+ abbrev "1"
+ osenv "^0.1.4"
+
+nopt@~3.0.6, "nopt@2 || 3":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+ dependencies:
+ abbrev "1"
+
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.6.tgz#498fa420c96401f787402ba21e600def9f981fff"
+ dependencies:
+ hosted-git-info "^2.1.4"
+ is-builtin-module "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
+normalize-path@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a"
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+
+npmlog@^4.0.0, npmlog@^4.0.2, "npmlog@0 || 1 || 2 || 3 || 4":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f"
+ dependencies:
+ are-we-there-yet "~1.1.2"
+ console-control-strings "~1.1.0"
+ gauge "~2.7.1"
+ set-blocking "~2.0.0"
+
+num2fraction@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+
+oauth-sign@~0.8.1:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+object-assign@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
+
+object-assign@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+
+object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
+object-keys@^1.0.10, object-keys@^1.0.8:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
+object.assign@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc"
+ dependencies:
+ define-properties "^1.1.2"
+ function-bind "^1.1.0"
+ object-keys "^1.0.10"
+
+object.omit@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
+ dependencies:
+ for-own "^0.1.4"
+ is-extendable "^0.1.1"
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ dependencies:
+ ee-first "1.1.1"
+
+once@^1.3.0, once@^1.3.3, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+once@~1.3.0:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
+ dependencies:
+ wrappy "1"
+
+onetime@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+
+openport@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/openport/-/openport-0.0.4.tgz#1d6715d8a8789695f985fa84f68dd4cd1ba426cb"
+
+optional@^0.1.0:
+ version v0.1.3
+ resolved "https://registry.yarnpkg.com/optional/-/optional-0.1.3.tgz#f87537517b59a5e732cfd8f18e4f7eea7ab4761e"
+
+optipng-bin@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/optipng-bin/-/optipng-bin-3.1.2.tgz#18c5a3388ed5d6f1e6ef1998ab0a6bcc8bdd0ca0"
+ dependencies:
+ bin-build "^2.0.0"
+ bin-wrapper "^3.0.0"
+ logalot "^2.0.0"
+
+ordered-read-streams@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b"
+ dependencies:
+ is-stream "^1.0.1"
+ readable-stream "^2.0.1"
+
+os-browserify@~0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54"
+
+os-filter-obj@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/os-filter-obj/-/os-filter-obj-1.0.3.tgz#5915330d90eced557d2d938a31c6dd214d9c63ad"
+
+os-homedir@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ dependencies:
+ lcid "^1.0.0"
+
+os-tmpdir@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+osenv@^0.1.4, osenv@0:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
+outpipe@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2"
+ dependencies:
+ shell-quote "^1.4.2"
+
+pako@~0.2.0:
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
+
+parents@^1.0.0, parents@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751"
+ dependencies:
+ path-platform "~0.11.15"
+
+parse-asn1@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
+ dependencies:
+ asn1.js "^4.0.0"
+ browserify-aes "^1.0.0"
+ create-hash "^1.1.0"
+ evp_bytestokey "^1.0.0"
+ pbkdf2 "^3.0.3"
+
+parse-glob@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
+ dependencies:
+ glob-base "^0.3.0"
+ is-dotfile "^1.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.0"
+
+parse-json@^2.1.0, parse-json@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
+ dependencies:
+ error-ex "^1.2.0"
+
+parseurl@~1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
+
+path-browserify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
+
+path-dirname@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
+
+path-exists@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+ dependencies:
+ pinkie-promise "^2.0.0"
+
+path-is-absolute@^1.0.0, path-is-absolute@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
+path-platform@~0.11.15:
+ version "0.11.15"
+ resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2"
+
+path-type@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
+ dependencies:
+ graceful-fs "^4.1.2"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+pbkdf2@^3.0.3:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.9.tgz#f2c4b25a600058b3c3773c086c37dbbee1ffe693"
+ dependencies:
+ create-hmac "^1.1.2"
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+
+performance-now@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+
+pify@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+postcss-value-parser@^3.2.3:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
+
+postcss@^5.0.0, postcss@^5.2.16:
+ version "5.2.16"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.16.tgz#732b3100000f9ff8379a48a53839ed097376ad57"
+ dependencies:
+ chalk "^1.1.3"
+ js-base64 "^2.1.9"
+ source-map "^0.5.6"
+ supports-color "^3.2.3"
+
+prepend-http@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+
+preserve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+
+pretty-bytes@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
+ dependencies:
+ get-stdin "^4.0.1"
+ meow "^3.1.0"
+
+pretty-bytes@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-3.0.1.tgz#27d0008d778063a0b4811bb35c79f1bd5d5fbccf"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+process-nextick-args@~1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+process@~0.11.0:
+ version "0.11.9"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
+
+pseudomap@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
+public-encrypt@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
+ dependencies:
+ bn.js "^4.1.0"
+ browserify-rsa "^4.0.0"
+ create-hash "^1.1.0"
+ parse-asn1 "^5.0.0"
+ randombytes "^2.0.1"
+
+punycode@^1.3.2, punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+punycode@1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
+
+q@^1.1.2:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
+
+qs@~5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-5.1.0.tgz#4d932e5c7ea411cca76a312d39a606200fd50cd9"
+
+qs@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
+qs@5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be"
+
+querystring-es3@~0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
+
+querystring@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
+
+randomatic@^1.1.3:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb"
+ dependencies:
+ is-number "^2.0.2"
+ kind-of "^3.0.2"
+
+randombytes@^2.0.0, randombytes@^2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
+
+raw-body@~2.1.5:
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774"
+ dependencies:
+ bytes "2.4.0"
+ iconv-lite "0.4.13"
+ unpipe "1.0.0"
+
+rc@^1.1.2, rc@^1.1.7:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea"
+ dependencies:
+ deep-extend "~0.4.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+read-all-stream@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
+ dependencies:
+ pinkie-promise "^2.0.0"
+ readable-stream "^2.0.0"
+
+read-only-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0"
+ dependencies:
+ readable-stream "^2.0.2"
+
+read-pkg-up@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
+ dependencies:
+ find-up "^1.0.0"
+ read-pkg "^1.0.0"
+
+read-pkg@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
+ dependencies:
+ load-json-file "^1.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^1.0.0"
+
+readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2:
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816"
+ dependencies:
+ buffer-shims "^1.0.0"
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+"readable-stream@>=1.0.33-1 <1.1.0-0":
+ version "1.0.34"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readable-stream@~1.1.9:
+ version "1.1.14"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readable-stream@~2.0.0:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+readdirp@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
+ dependencies:
+ graceful-fs "^4.1.2"
+ minimatch "^3.0.2"
+ readable-stream "^2.0.2"
+ set-immediate-shim "^1.0.1"
+
+redent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
+ dependencies:
+ indent-string "^2.1.0"
+ strip-indent "^1.0.1"
+
+regex-cache@^0.4.2:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
+ dependencies:
+ is-equal-shallow "^0.1.3"
+ is-primitive "^2.0.0"
+
+repeat-element@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
+
+repeat-string@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+ dependencies:
+ is-finite "^1.0.0"
+
+replace-ext@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
+
+request@^2.61.0, request@^2.81.0, request@2:
+ version "2.81.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~2.1.1"
+ har-validator "~4.2.1"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ oauth-sign "~0.8.1"
+ performance-now "^0.2.0"
+ qs "~6.4.0"
+ safe-buffer "^5.0.1"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.0.0"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+
+require-main-filename@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+
+resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235"
+ dependencies:
+ path-parse "^1.0.5"
+
+resolve@~1.1.0, resolve@~1.1.6, resolve@1.1.7:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+
+right-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
+ dependencies:
+ align-text "^0.1.1"
+
+rimraf@^2.2.6, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.6.1, rimraf@2:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
+ dependencies:
+ glob "^7.0.5"
+
+rimraf@~2.2.8:
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
+
+ripemd160@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-1.0.1.tgz#93a4bbd4942bc574b69a8fa57c71de10ecca7d6e"
+
+rtlcss@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-2.1.2.tgz#a5626edc9e7f6017f2d8df30c8bf622bd193a8fc"
+ dependencies:
+ chalk "^1.0.0"
+ findup "^0.1.5"
+ mkdirp "^0.5.1"
+ postcss "^5.0.0"
+ strip-json-comments "^2.0.0"
+
+safe-buffer@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+
+sass-graph@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.1.2.tgz#965104be23e8103cb7e5f710df65935b317da57b"
+ dependencies:
+ glob "^7.0.0"
+ lodash "^4.0.0"
+ yargs "^4.7.1"
+
+sax@~1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
+
+seek-bzip@^1.0.3:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
+ dependencies:
+ commander "~2.8.1"
+
+semver-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9"
+
+semver-truncate@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-1.1.2.tgz#57f41de69707a62709a7e0104ba2117109ea47e8"
+ dependencies:
+ semver "^5.3.0"
+
+semver@^4.0.3:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
+
+semver@^5.3.0, semver@~5.3.0, "semver@2 || 3 || 4 || 5":
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+
+set-blocking@^2.0.0, set-blocking@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+
+set-immediate-shim@^1.0.0, set-immediate-shim@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
+
+sha.js@^2.3.6, sha.js@~2.4.4:
+ version "2.4.8"
+ resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f"
+ dependencies:
+ inherits "^2.0.1"
+
+shasum@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f"
+ dependencies:
+ json-stable-stringify "~0.0.0"
+ sha.js "~2.4.4"
+
+shell-quote@^1.4.2, shell-quote@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
+ dependencies:
+ array-filter "~0.0.0"
+ array-map "~0.0.0"
+ array-reduce "~0.0.0"
+ jsonify "~0.0.0"
+
+signal-exit@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+
+sntp@1.x.x:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
+ dependencies:
+ hoek "2.x.x"
+
+source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+
+source-map@0.4.x:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ dependencies:
+ amdefine ">=0.0.4"
+
+sparkles@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3"
+
+spdx-correct@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+ dependencies:
+ spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+
+squeak@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/squeak/-/squeak-1.3.0.tgz#33045037b64388b567674b84322a6521073916c3"
+ dependencies:
+ chalk "^1.0.0"
+ console-stream "^0.1.1"
+ lpad-align "^1.0.1"
+
+sshpk@^1.7.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.11.0.tgz#2d8d5ebb4a6fab28ffba37fa62a90f4a3ea59d77"
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ dashdash "^1.12.0"
+ getpass "^0.1.1"
+ optionalDependencies:
+ bcrypt-pbkdf "^1.0.0"
+ ecc-jsbn "~0.1.1"
+ jodid25519 "^1.0.0"
+ jsbn "~0.1.0"
+ tweetnacl "~0.14.0"
+
+stack-trace@0.0.9:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695"
+
+stat-mode@^0.2.0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502"
+
+statuses@1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
+
+stream-browserify@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "^2.0.2"
+
+stream-combiner2@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe"
+ dependencies:
+ duplexer2 "~0.1.0"
+ readable-stream "^2.0.2"
+
+stream-http@^2.0.0:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3"
+ dependencies:
+ builtin-status-codes "^3.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.1.0"
+ to-arraybuffer "^1.0.0"
+ xtend "^4.0.0"
+
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+stream-splicer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83"
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^2.0.2"
+
+string_decoder@~0.10.0, string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+
+string-width@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+stringstream@~0.0.4:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-bom-stream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee"
+ dependencies:
+ first-chunk-stream "^1.0.0"
+ strip-bom "^2.0.0"
+
+strip-bom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+ dependencies:
+ is-utf8 "^0.2.0"
+
+strip-dirs@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-1.1.1.tgz#960bbd1287844f3975a4558aa103a8255e2456a0"
+ dependencies:
+ chalk "^1.0.0"
+ get-stdin "^4.0.1"
+ is-absolute "^0.1.5"
+ is-natural-number "^2.0.0"
+ minimist "^1.1.0"
+ sum-up "^1.0.1"
+
+strip-indent@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+ dependencies:
+ get-stdin "^4.0.1"
+
+strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
+strip-outer@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.0.tgz#aac0ba60d2e90c5d4f275fd8869fd9a2d310ffb8"
+ dependencies:
+ escape-string-regexp "^1.0.2"
+
+subarg@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
+ dependencies:
+ minimist "^1.1.0"
+
+sum-up@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sum-up/-/sum-up-1.0.3.tgz#1c661f667057f63bcb7875aa1438bc162525156e"
+ dependencies:
+ chalk "^1.0.0"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+supports-color@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+ dependencies:
+ has-flag "^1.0.0"
+
+svgo@^0.6.0:
+ version "0.6.6"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.6.6.tgz#b340889036f20f9b447543077d0f5573ed044c08"
+ dependencies:
+ coa "~1.0.1"
+ colors "~1.1.2"
+ csso "~2.0.0"
+ js-yaml "~3.6.0"
+ mkdirp "~0.5.1"
+ sax "~1.2.1"
+ whet.extend "~0.9.9"
+
+syntax-error@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.3.0.tgz#1ed9266c4d40be75dc55bf9bb1cb77062bb96ca1"
+ dependencies:
+ acorn "^4.0.3"
+
+tar-pack@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
+ dependencies:
+ debug "^2.2.0"
+ fstream "^1.0.10"
+ fstream-ignore "^1.0.5"
+ once "^1.3.3"
+ readable-stream "^2.1.4"
+ rimraf "^2.5.1"
+ tar "^2.2.1"
+ uid-number "^0.0.6"
+
+tar-stream@^1.1.1:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf"
+ dependencies:
+ bl "^1.0.0"
+ end-of-stream "^1.0.0"
+ readable-stream "^2.0.0"
+ xtend "^4.0.0"
+
+tar@^2.0.0, tar@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.2"
+ inherits "2"
+
+tempfile@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-1.1.1.tgz#5bcc4eaecc4ab2c707d8bc11d99ccc9a2cb287f2"
+ dependencies:
+ os-tmpdir "^1.0.0"
+ uuid "^2.0.1"
+
+"through@>=2.2.7 <3":
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+
+through2-filter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec"
+ dependencies:
+ through2 "~2.0.0"
+ xtend "~4.0.0"
+
+through2@^0.6.0, through2@^0.6.1:
+ version "0.6.5"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
+ dependencies:
+ readable-stream ">=1.0.33-1 <1.1.0-0"
+ xtend ">=4.0.0 <4.1.0-0"
+
+through2@^2.0.0, through2@~2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
+ dependencies:
+ readable-stream "^2.1.5"
+ xtend "~4.0.1"
+
+time-stamp@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.0.1.tgz#9f4bd23559c9365966f3302dbba2b07c6b99b151"
+
+timed-out@^3.0.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217"
+
+timers-browserify@^1.0.1:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d"
+ dependencies:
+ process "~0.11.0"
+
+tiny-lr@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-0.2.1.tgz#b3fdba802e5d56a33c2f6f10794b32e477ac729d"
+ dependencies:
+ body-parser "~1.14.0"
+ debug "~2.2.0"
+ faye-websocket "~0.10.0"
+ livereload-js "^2.2.0"
+ parseurl "~1.3.0"
+ qs "~5.1.0"
+
+to-absolute-glob@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f"
+ dependencies:
+ extend-shallow "^2.0.1"
+
+to-arraybuffer@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
+
+tough-cookie@~2.3.0:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
+ dependencies:
+ punycode "^1.4.1"
+
+trim-newlines@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+
+trim-repeated@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21"
+ dependencies:
+ escape-string-regexp "^1.0.2"
+
+tty-browserify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
+
+tunnel-agent@^0.4.0:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ dependencies:
+ safe-buffer "^5.0.1"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+
+type-is@~1.6.10:
+ version "1.6.14"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2"
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.13"
+
+typedarray@^0.0.6, typedarray@~0.0.5:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+
+uglify-js@~2.8.3:
+ version "2.8.15"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.15.tgz#835dd4cd5872554756e6874508d0d0561704d94d"
+ dependencies:
+ source-map "~0.5.1"
+ yargs "~3.10.0"
+ optionalDependencies:
+ uglify-to-browserify "~1.0.0"
+
+uglify-to-browserify@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+
+uid-number@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+
+umd@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.1.tgz#8ae556e11011f63c2596708a8837259f01b3d60e"
+
+underscore.string@~3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.2.3.tgz#806992633665d5e5fcb4db1fb3a862eb68e9e6da"
+
+unique-stream@^2.0.2:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369"
+ dependencies:
+ json-stable-stringify "^1.0.0"
+ through2-filter "^2.0.0"
+
+unpipe@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+unzip-response@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe"
+
+uri-path@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/uri-path/-/uri-path-1.0.0.tgz#9747f018358933c31de0fccfd82d138e67262e32"
+
+url-parse-lax@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
+ dependencies:
+ prepend-http "^1.0.1"
+
+url-regex@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/url-regex/-/url-regex-3.2.0.tgz#dbad1e0c9e29e105dd0b1f09f6862f7fdb482724"
+ dependencies:
+ ip-regex "^1.0.1"
+
+url@~0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
+ dependencies:
+ punycode "1.3.2"
+ querystring "0.2.0"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+util@~0.10.1, util@0.10.3:
+ version "0.10.3"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
+ dependencies:
+ inherits "2.0.1"
+
+uuid@^2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
+
+uuid@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
+
+vali-date@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6"
+
+validate-npm-package-license@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
+ dependencies:
+ spdx-correct "~1.0.0"
+ spdx-expression-parse "~1.0.0"
+
+verror@1.3.6:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c"
+ dependencies:
+ extsprintf "1.0.2"
+
+vinyl-assign@^1.0.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/vinyl-assign/-/vinyl-assign-1.2.1.tgz#4d198891b5515911d771a8cd9c5480a46a074a45"
+ dependencies:
+ object-assign "^4.0.1"
+ readable-stream "^2.0.0"
+
+vinyl-fs@^2.1.1, vinyl-fs@^2.2.0:
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239"
+ dependencies:
+ duplexify "^3.2.0"
+ glob-stream "^5.3.2"
+ graceful-fs "^4.0.0"
+ gulp-sourcemaps "1.6.0"
+ is-valid-glob "^0.3.0"
+ lazystream "^1.0.0"
+ lodash.isequal "^4.0.0"
+ merge-stream "^1.0.0"
+ mkdirp "^0.5.0"
+ object-assign "^4.0.0"
+ readable-stream "^2.0.4"
+ strip-bom "^2.0.0"
+ strip-bom-stream "^1.0.0"
+ through2 "^2.0.0"
+ through2-filter "^2.0.0"
+ vali-date "^1.0.0"
+ vinyl "^1.0.0"
+
+vinyl@^0.4.3:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847"
+ dependencies:
+ clone "^0.2.0"
+ clone-stats "^0.0.1"
+
+vinyl@^0.5.0:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde"
+ dependencies:
+ clone "^1.0.0"
+ clone-stats "^0.0.1"
+ replace-ext "0.0.1"
+
+vinyl@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884"
+ dependencies:
+ clone "^1.0.0"
+ clone-stats "^0.0.1"
+ replace-ext "0.0.1"
+
+vm-browserify@~0.0.1:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
+ dependencies:
+ indexof "0.0.1"
+
+ware@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/ware/-/ware-1.3.0.tgz#d1b14f39d2e2cb4ab8c4098f756fe4b164e473d4"
+ dependencies:
+ wrap-fn "^0.1.0"
+
+watchify@^3.6.1:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/watchify/-/watchify-3.9.0.tgz#f075fd2e8a86acde84cedba6e5c2a0bedd523d9e"
+ dependencies:
+ anymatch "^1.3.0"
+ browserify "^14.0.0"
+ chokidar "^1.0.0"
+ defined "^1.0.0"
+ outpipe "^1.1.0"
+ through2 "^2.0.0"
+ xtend "^4.0.0"
+
+websocket-driver@>=0.5.1:
+ version "0.6.5"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36"
+ dependencies:
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
+
+whet.extend@~0.9.9:
+ version "0.9.9"
+ resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
+
+which-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+
+which@^1.2.9, which@~1.2.1, which@1:
+ version "1.2.14"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
+ dependencies:
+ isexe "^2.0.0"
+
+wide-align@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad"
+ dependencies:
+ string-width "^1.0.1"
+
+window-size@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
+
+window-size@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+
+wordwrap@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+
+wrap-ansi@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+
+wrap-fn@^0.1.0:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/wrap-fn/-/wrap-fn-0.1.5.tgz#f21b6e41016ff4a7e31720dbc63a09016bdf9845"
+ dependencies:
+ co "3.1.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+xtend@^4.0.0, "xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.0, xtend@~4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
+
+y18n@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+
+yallist@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+
+yargs-parser@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4"
+ dependencies:
+ camelcase "^3.0.0"
+ lodash.assign "^4.0.6"
+
+yargs@^4.7.1:
+ version "4.8.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0"
+ dependencies:
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ lodash.assign "^4.0.3"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.1"
+ which-module "^1.0.0"
+ window-size "^0.2.0"
+ y18n "^3.2.1"
+ yargs-parser "^2.4.1"
+
+yargs@~3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
+ dependencies:
+ camelcase "^1.0.2"
+ cliui "^2.1.0"
+ decamelize "^1.0.0"
+ window-size "0.1.0"
+
+yauzl@^2.2.1:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.7.0.tgz#e21d847868b496fc29eaec23ee87fdd33e9b2bce"
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.0.1"
+