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 ); + + ?> + + + + display_headers(); ?> + + + user_id; + $points = $data->total; + + $user = get_userdata( $user_id ); + + ?> + + + + + + + + + + + display_headers(); ?> + +
+ + + + display_name + , $user_id + , 'display' + ); + + /** + * Filters a user's name in the top users in period table. + * + * @since 1.0.0 + * + * @param string $name The name of the user. + * @param int $user_id The user ID. + * @param string|null $points_type The points type the query is for. + * @param WordPoints_Top_Users_In_Period_Query $query The query. + * @param string $context The context in which the table is being displayed. + */ + $name = apply_filters( + 'wordpoints_top_users_in_period_table_username' + , $name + , $user_id + , $this->points_type + , $this->query + , $this->context + ); + + echo wp_kses( + $name + , 'wordpoints_top_users_in_period_table_username' + ); + + ?> + + points_type ) { + + echo wordpoints_format_points( + $points + , $this->points_type + , "top_users_in_period_table_{$this->context}" + ); + + } else { + + echo (int) $points; + } + + ?> +
+ + _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['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" +